Ask HN: Lisp eval vs. Lisp macros. Are they the same underlying concept?
9 behnamoh 10 5/30/2025, 2:05:50 PM
Is my understanding correct that Lisp's powerful macro system stems from the ability to write the eval function in Lisp itself? From what I gather, Lisp starts with a small set of primitives and special forms—seven in the original Lisp, including lambda. I recall Paul Graham demonstrating in one of his essays that you can build an eval function using just these primitives. Those primitives are typically implemented in a host language like C, but once you have an eval function in Lisp, you can extend it with new rules. The underlying C interpreter only sees the primitives, but as a programmer, you can introduce new syntax rules via eval. This seems like a way to understand macros, where you effectively add new language rules. I know Lisp macros are typically defined using specific keywords like defmacro, but is the core idea similar—extending the language by building on the eval function with new rules?
When a macro is used for any given purpose, what happens is a bit more general because it first "does something with" its argument (rather than necessarily straight-up evaluating it). Then the result of that is evaluated.
Without macros you could implement eval still but your internal lisp implementation could only work on quoted s-expressions, there would be no way to get back to the base unquoted level of lisp code (assuming you can't use the primitive eval, since you're trying to implement lisp in lisp).
Another use case is implementing a shortcutting boolean and function. You can't do the shortcutting without macros because all of the arguments get eval'd before passing to your and function.
https://www.lispworks.com/documentation/HyperSpec/Body/f_eva...
eval
Runs at runtime.
Takes a data structure (usually a list) and evaluates it as Lisp code.
Example: (eval '(+ 1 2)) ; => 3
Use case: Dynamically construct and run code while the program is running.
Macros
Runs at compile time (or macro-expansion time).
Transform Lisp code before it's evaluated. They return new code (a Lisp form) to be compiled/evaluated later.
Example: (defmacro my-unless (condition body) `(if (not ,condition) ,body))
(my-unless (= 1 2) (print "Not equal")) ; expands to (if (not (= 1 2)) (print ...))
Use case: Extend the language by defining new syntactic constructs. Enables powerful DSLs and optimizations.
Macros are really only instructions for the compiler, how to compile things faster.
The syntax improvement aspect is minuscule, because Lisp has no actual syntax perse.
Macros can stage calculations to compile time. Compile time can happen in a completely different environment from run-time, such as on a different machine. It only happens once, whereas run-time can repeat many times.
A macro can be designed to that it opens a specific file, reads the content and generates some code based on that (or simply includes the file as a literal constant). That file exists only on the build machine, perhaps part of the source tree of the program. Thus, compiled code containing the macro call can run anyhere, but source code containing the macro cannot be evaled anywhere.