What is the point of Lisp?

View source | View history | Atom feed for this file

(originally written on 2023-12-27)

i hate lisp, especially lisp-2, but even lisp-1, because my brain doesn’t want to understand quote. what follows is some introspection i did to understand quote better; by the end i had a deeper appreciation of what the point of lisp was, and some thoughts on how to improve it.

i think the thing that’s weird about scheme is that in most languages, many expressions (in particular, lists) evaluate to themselves. e.g. in both python and haskell, [1,2,3] evaluates to [1,2,3], i.e., itself! but in scheme, (1 2 3) does not evaluate to itself… because it tries to call the function 1 on the arguments 2 and 3, which errors because 1 is not a function. so to get the evaluated list (1 2 3), you need to write '(1 2 3). but importantly, '(1 2 3) does not evaluate to itself either; it evaluates to the list (1 2 3). otoh, some things like 2 still evaluate to themselves.

this “lists don’t just evaluate to themselves” is what makes lisps very hard to work with, i think. and ultimately this comes from the abuse of notation – using (a b c) for both function calls and for data (or as paul graham says “[…] one of the most distinctive features of Lisp: code and data are made out of the same data structures”). that’s why lisp needs the ' (or quote) to distinguish between data and programs, because the programs look just like the data!

i wonder why michael nielsen did not make this point in his “maxwell’s laws for computer science” essay. (update 2024-03-02: i guess he probably thought it was obvious because paul graham says it on page 3 of his lisp article…)

2024-01-09: maybe this is the whole point though? because then you can manipulate function calls as if they’re data? (otherwise you’d need a separate syntax to be able to manipulate the syntax tree.) i don’t have a good idea of where that’d be a useful thing to do though. like, you want to swap out the function but keep the arguments the same? why would you want to do that? in haskell or python you don’t need to do this ever…

and i guess they chose the '( ... ) notation because then you don’t need to backslash-escape everything.

so in lisp, you can easily do things like “take this function, and anywhere it calls sin(x), you should replace it with sin(sin(x))”. but in haskell you can just replace sin with an argument f and then just pass in sin . sin instead of sinmanipulating programs just doesn’t seem very useful, because you can just edit the programs in your editor instead of modifying them in the interpreter! whereas data can be very large so you don’t want to manually edit it. that’s the whole point of having a computer! so it seems to come down to this: programs are authored by humans, so by assumption they are compact enough to edit by hand, so therefore you don’t benefit from having the same syntax for programs (esp. function calls) and data.

however, i should still go find some real examples of programs written in idiomatic lisp where the manipulation of programs is done in an essential manner, if such a thing even exists (no one has ever shown me such a thing, but then again i don’t really hang out with people who know a lot about Lisp). like, if it did exist, then it would be much harder to write it in haskell or something. so i’m doubtful such a thing can exist.

ok i guess maybe a REAL example is if you’re writing an interpreter? in that case, data=program. it’s almost as if lisp was specifically designed to write its own interpreter…

but wait! michael nielsen wrote a lisp interpreter in python no problem. so lisp is a language that it’s easy to write an interpreter for, but not necessarily easier to write an interpreter in.

(update 2024-03-02: after i wrote the above, i read paul graham’s essay which says “The unusual thing about Lisp – in fact, the defining quality of Lisp – is that it can be written in itself.” Is the whole point of Lisp just this self-referential madness of “it is the programming language such that it allows you to extremely trivially write an interpreter of that very programming language”?)

you know, i think the best of both worlds might have been using [...] for lists and (...) for function calls, and then just not having quote. then [1 2 3] evaluates to [1 2 3]. but (f 1 2 3) will do a function call. [1 2 3] is shorthand for (cons 1 (cons 2 (cons 3 []))). car and cdr can only be used on [...] brackets (just like (car 3) errors in scheme, so would (car ( ... )) in this hypothetical language). then you can STILL easily write an interpreter for this language because all the brackets are there – no need to do fancy parsing. BUT the big advantage would be that you don’t have quote, and there is clear separation between data and code: data evaluate to themselves, code evaluates down to something else.

you wouldn’t easily be able to write an interpreter in this language unless support for strings is added – because there’s no way to pass around function calls without evaluating them. but in python you would just as easily write one, i think.

2024-01-10: also, there’d be currying so (f x y z) would be the same thing as (((f x) y) z). haskell does make this much nicer by not requiring the parentheses. but that makes parsing much harder.