I’m trying to understand if it’s possible/good practice to write macros that expand differently depending on whether the arguments are constants or symbols. As an example (in Emacs 30.0.50):
(defmacro list-length (x) (cond (`(symbolp ,x) `(eval (length ,x))) (t (length x))))
The different ways I’d expect it to be expanded are as follows:
(let ((x (list 1 2 3 4 5))) (macroexpand '(list-length x))) => (eval (length x))
(macroexpand '(list-length (1 2 3 4))) => 4
A more real-world use case for myself is in something like https://github.com/nealsid/simpleproj/blob/main/util.el#L18, and what I’d like to do in particular is either expand to several calls to
puthash if the macro arguments can be determined at expansion time, or otherwise expand to a cl-loop which processes elements of the list passed in at run-time. What exactly “determined at expansion time” means is a bit fuzzy to me (symbolp? Boundp? Or if there is something more I am missing). Thanks!
The code you quoted follows this structure:
(defun fun (&rest args) (loop for (k v) on args by 'cddr do ...))
Let's assume first that you rename the function
fun% instead, and define a macro named
fun, which at first can be as simple as that:
(defmacro fun (&rest args) `(fun% ,@args))
Each time you invoke
fun directly, you will know how many parameters you have:
(fun :a 0 :b 1)
what I’d like to do in particular is either expand to several calls to puthash if the macro arguments can be determined at expansion time, or otherwise expand to a cl-loop which processes elements of the list passed in at run-time.
Syntactically, this is a call with 4 arguments, the macro can access it through
args variable which is bound to
(:a 0 :b 1).
So there is no case where the macro doesn't know how many arguments are passed to the call. Only the function
fun% can receive an arbitrary number of arguments when you call it using
(let ((list (list :a 0 :b 1))) (apply 'fun% list))
But macros cannot be applied like that (you generally don't invoke the macro-function yourself, the macroexpansion mechanism do it for you).
The situation would be different if
args was not a
&rest argument, if you had instead:
(defmacro fun (args) ....)
Here you could only invoke
fun with exactly one argument:
You have two common situations, both of which allow you to either optimize during expansion or defer to
fun to have the same evaluation mechanism as usual functions, ie. you want the
<list> expression to be evaluated as-is at runtime, and then use the value to populate the list. If you do so, then you can optimize for some known cases, like the list being constant (it is a quoted list). You will have to defer to a call to
foo% anytime you don't know if
args is a constant list or not.
You are defining a special form where standard evaluation rules no longer apply. For example, you decide that the set of keys is probably always the same, and only the values need to be evaluated. You specify your own grammar:
ARGS ::= SYMBOL | KW-LIST KW-LIST := (KEYWORD FORM . KW-LIST) | NIL
KEYWORD items are expected to be literal symbols, but FORM values must be injected in the generated code at the place where their value is needed (in the calls to
Valid calls are:
(foo ()) (foo (:a 10 :b (+ 10 20) :c (* 5 30))) (foo my-args)
Note how the list is not quoted, it is part of the syntax of
foo, it can be parsed at runtime and you can know its size, the only unknonw things in the macro are the values associated with the fixed set of keys.
Both are possible, you generally never need to call
eval, because how could the compiler ever know what the values are going to be when the code is run, in a totally different environment? Consider this Common Lisp code:
(progn (print "Enter a list: ") (let ((list (read))) (fun list)))
fun is a macro, you cannot know how many items the
list has, this is something that will be known after the user answer the prompt.