Hacker News new | ask | show | jobs
by kazinator 11 days ago
Lisp-1 has the wart that it models special operators as bindings in a variable name space. So a form like (print let) actually resolves let to a binding to a special operator, and then has to be somehow pronounced as nonsense. And you can block let from working by binding that as a variable name. Allowing variables to shadow the basic operators of the language is quirky.

Would you want to use a weird POSIX shell in which "for x in *.jpg; ..." stopped working because you assigned "for=42"?

Yet Lisp-1 has a notational advantage for programs that work with functional values; programs that indirect upon functions are more succinct, free of "shim" operators for lifting values out of the function binding space, or requesting application of a function value.

In the TXR Lisp dialect, I worked out a way to have the notational convenience, without bringing in the Lisp-1 issue.

1. The substrate, including macro-expansion logic, is thoroughly Lisp-2.

2. When a compound form is written in square brackets, it indicates that immediate elements (those constituents that are symbols) are to be looked up in a single namespace that is a merger of the function and variable namespaces according to precisely documented rules. This is not recursively applied to the form; its arguments that are compound forms are not treated this way unless they use square brackets.

3. Macros are not affected. In a form [a b c ...], the element a is neither recognized as a special operator or as an operator macro. If it is a symbol, it is either a variable, or a symbol macro.

4. [ ... ] is a surface syntax with a straightforward representation: the (dwim ...) operator. The above semantics is thoroughly baked into the macro expander and correctly treated at all necessary levels of the language: the handling of lexical scopes at expansion time and compilation/evaluation.

This system leaves mainly just one small infelicity. The merged 1+2 namespace is not available in certain contexts when it would be handy. Like say we are writing a function that takes a functional argument, that we would like to default:

   (defun my-sort (sequence : (test-fn equal)) ...)   ;; nope!
There is no variable equal. We cannot do this:

   (defun my-sort (sequence : [test-fn equal]) ...)   ;; nope!
because the (test-fn equal) syntax is not a form to be evaluated; it is just notation within the parameter list which has not been equipped with support for the alternative brackets/dwim structure. The way to do this is:

   (defun my-sort (sequence : (test-fn (fun equal))) ...) 
fun* is like Common Lisp's function* operator. It's one of the few instances you ever have to use it, the others also being situations like values in binding constructs such as let. It is not worth complicating things to provide a way to take the fun out these situations.