eminent101 · 2022-08-12 01:11
Run `M-x ielm RET`.
Then run this Elisp code:
(10 . 20)
I get this error:
ELISP> (10 . 20)
*** Eval error *** Wrong type argument: listp, 20
I get that what I am doing is nonsense. `10` is not a function so this can never run.
But I don't understand what's with the error message. Why doesn't Elisp interpreter complain about `10` not being a function? Why is it complaining about `listp` and `20`?
franburstall · 2022-08-12 01:59
Lisp will try to evaluate the list of arguments before worrying about the function. It expects to find a (possibly empty) list of them in the `cdr` of `(10 . 20)` but gets `20`.
ieure · 2022-08-12 03:20
Expanding on this answer a bit. `(10 . 20)` is a cons cell, which is the element lists are built out of. The cons cell *itself* is a list:
(listp '(10 . 20)) ;; → t
… but its cdr is not:
(listp (cdr '(10 . 20))) ;; → nil
Convention is that arbitrary-length sequences must be composed of cons cells where the cdr is another list; `nil` is the empty list:
(listp nil) ;; → t
…and its position in the cdr of a cell indicates the end of the list. The shorthand:
(1 2 3 4 5)
is equivalent to the more explicit (and very verbose):
(1 . (2 . (3 . (4 . (5 . nil)))))
(equal '(1 2 3 4 5) '(1 . (2 . (3 . (4 . (5 . nil)))))) ;; → t
While you'd never want to use that in code, it's helpful to illuminate what's going on under the covers. Your example fails because your cdr isn't a list, it's the value `20`. If you were to define `10`[1] as a 0-arity function[2], you'd call it as `(10)`, which is equivalent to `(10 . nil)` — and that `nil` is the empty argument list given to the 0-arity function. If you were to define it as a 1-arity function, you might call it as `(10 20)`, which is equivalent to `(10 . (20 . nil))` — and in this case the inner cons cell is the list of arguments the function is applied to, a list of one element, the value `20`.
Cons cells are also useful for situations where you need a 2-tuple — something which can hold two and *only* two elements — such as alists, where the car is the key and the cdr is the value.
[1]: You can't define a function named `10`, but, just for example.
[2]: That is, a function which accepts zero arguments.
eminent101 · 2022-08-12 08:11
Thank you for the great answer! The strange error no longer looks strange! :-)
eleven_cupfuls · 2022-08-12 16:59
Aside from the quote+dot syntax, there is also a function to construct a cons cell: `(cons 10 20)` -> `(10 . 20)`. This is useful when you are handling variables instead of literal values. E.g.
(let ((value 10)
(other-value 20))
(cons value other-value))
also evaluates to `(10 . 20)` (Otherwise you would write
`(,value . ,other-value)
[deleted] · 2022-09-06 02:56
[deleted]
ieure · 2022-09-06 05:10
> Would you recommend S-expressions as a core feature in a new language?
I don't think I can give a fair answer. I'd have to see how the feature integrates into the language as a whole. The framing of sexprs as "a core feature" is odd. In Lisps, sexprs aren't a language feature, they're the language.
> Even if the main syntax is more C-like, would an S-expr IR that you can use with macros be useful to you?
Again, I'd really have to see the actual language. But my kneejerk reaction is that I don't think it'd be useful.
The power of Lisp's macro system derives from the way writing s-expressions *directly* eliminates the difference between syntax and AST. In any other language, there's a huge gap between the syntax and the AST representing it, which makes it difficult to use the AST as a programmer. In Lisp, this simply doesn't exist, because you're essentially writing the AST yourself, all the time. Manipulating the AST is no more difficult than refactoring a program, because it's the same thing.
A C syntax on top of a sexp IR doesn't seem like it adds any utility over a C syntax on top of any other kind of IR, because *any system* with a difference between its syntax and AST/IR is inherently more complex and difficult to use in this manner. Macros are stuck manipulating the AST, which bears little resemblance to the ordinary syntax, which makes it unreliable and bug-prone, because it's not always obvious what AST structures correspond to what syntax and vice-versa. The skill of writing programs in the surface syntax don't transfer to skill manipulating the AST.
If you want to build a non-Lisp language with good macro support, focus on usability first, and technology to get you there second. Selecting the IR is putting the cart before the horse. My best recommendation is to survey some languages which support macros.
I'm not sure where they landed, but Scala's experimental macro system ca. 2017-2018 seemed (at least superficially) decent. Code was treated as data and could be reasonably manipulated / have values substituted into it from the lexenv. It was much more complicated, and took years to implement (and felt like years to compile), but Scala was a profoundly complicated language to begin with.
Julia claims to have "Lisp-like" macros, and would be worth looking at.
Python took a different approach, and provides function decorators and context managers, both of which cover common macro usecases without being macros themselves, or requiring a proper macro system.
I wish you the best of luck with your project.