Improving Lisp Syntax Is Harder Than It Looks
Posted by Peter on March 17, 2006
One of the biggest complaints I hear (by biggest I mean most often) is that Lisp syntax is ugly. One common issue is that people from a mathematical background would like to invoke functions as name(parameters) instead of (name parameters). This kind of change wouldn’t be hard to make, and yet few lisp versions, except some of the very early variants and TwinLisp implement it.
The real motivation for leaving Lisp syntax as it is comes from macros. Under the current syntax macros can treat the code as a series of nested lists, which makes it easy to write intuitive looking macro expansions, for example if a macro expands into '(display "text") it is pretty obvious what it does. Although in theory you could keep this macro system with a new Lisp syntax it would look strange, and basically force users of the language to know both the old syntax and the new syntax. Thus we would expect the macros to read in the function call under this kind of syntax as some new kind of object, with one operation returning the function symbol, and another operation returning the list of parameters. Thus a macro expansion would have to look something like this: build-call('display '("text")). Not only does this expansion fail to visually look the same it also is much more complicated. One could try to get around this by altering the quasiquote operator, as in TwinLisp, so that the expansion becomes `display("text"), but then we have sacrificed the simplicity of the quasiquote, which no longer operates on lists. No matter how you solve the problem you end up in a bit of a bind.
Another disadvantage to this change of syntax is that it makes functional programming much more odd looking. Lets say you have a list containing functions and you want to call the first one. In Scheme you write ((car lst) params) and in Common Lisp (funcall (car lst) params). However in our new syntax it looks like: car(lst)(params) and funcall(car(lst) (params)). Neither of these is very elegant, and it only gets worse if that call in turn returns a function, which would look like: car(lst)(params)(params2) and funcall(funcall(car(lst) (params)) (params2)).
So changing lisp’s syntax, at least to make it more conventional in terms of function calls, is not really a win, although the average cases look slightly more “normal” more complicated operations become more convoluted. Of course that doesn’t necessarily mean that we can’t improve Lisp’s syntax at all, in a future post I will discuss the benefits and disadvantages of adding a block structure to Lisp (as proposed in Arc, TwinLisp, ect).
April 17, 2006 at 9:33 am
I cannot agree with the following:
“”"
One could try to get around this by altering the quasiquote operator, as in TwinLisp, so that the expansion becomes `display(”text”), but then we have sacrificed the simplicity of the quasiquote, which no longer operates on lists.
“”"
Quoting works in TwinLisp just like it in lisp. Let’s do an example with a backquote:
$ tlisp
TwinLisp interpreter.
Typing “Ctrl-D” or “exit” quits interpreter.
>>> a=1
1
>>> `~(a,b,$a)
(A B 1)
>>>
So, quotes act on lists *exactly* like in lisp, because all expansions of quotes are performed by lisp itself (CLisp in our example). It amazes me how much of lisp can be left the same while introducing a syntax.
April 17, 2006 at 10:16 am
In reply to
“”"
Another disadvantage to this change of syntax is that it makes functional programming much more odd looking. Lets say you have a list containing functions and you want to call the first one. In Scheme you write ((car lst) params) and in Common Lisp (funcall (car lst) params). However in our new syntax it looks like: car(lst)(params) and funcall(car(lst) (params)). Neither of these is very elegant, and it only gets worse if that call in turn returns a function, which would look like: car(lst)(params)(params2) and funcall(funcall(car(lst) (params)) (params2)).
“”"
Want to give TwinLisp version of (funcall (car lst) params). Here it is:
lst.car().funcall(params)
and with further funcall, if the first one returns a function:
lst.car().funcall(params).funcall(params2)
TwinLisp gives flexibility (choice) to write the same thing almost as you showed it above:
funcall(funcall(car(lst),params),params2)