Hello guys,
I have a question regarding the nested backquotes in macros. I wrote a macro, which creates lexical bindings for “port:ip” values:
(defun mkstr (&rest args)
(with-output-to-string (s)
(dolist (a args) (princ a s))))
(defun mksymb (&rest args)
(values (intern (string-upcase (apply #'mkstr args)))))
;; my macro
(defmacro with-free-ports (start end &body body)
(let ((range (loop for port from start to end collect (format NIL "127.0.0.1:~a" port)))
(n 0))
`(let ,(mapcar #'(lambda (p) `(,(mksymb "PORT-" (incf n)) ,p)) range)
(progn ,@body))))
One sets a range of ports on localhost and these ports are bound to symbols port-1, port-2, etc…
(with-free-ports 1 3 port-1) ;; => "127.0.0.1:1"
This works fine if the start
or end
parameters are given as values. But if they are variables. which must be evaluated, this macro doesn’t work:
(let ((start 1))
(with-free-ports start 3 port-1)) ;; error
In order to fix it, I made the let
- bindings a part of the macro-expansion:
(defmacro with-free-ports (start end &body body)
`(let ((range (loop for port from ,start to ,end collect (format NIL "127.0.0.1:~a" port)))
(n 0))
`(let ,(mapcar #'(lambda (p) `(,(mksymb "PORT-" (incf n)) ,p)) range)
(progn ,@body))))
but get a compilation warning that the body
is never used. I assume this is because of the inner backquote.
To evaluate ,@body
inside the inner backquote, I use one more comma, and the macro compiles without warnings:
(defmacro with-free-ports (start end &body body)
`(let ((range (loop for port from ,start to ,end collect (format NIL "127.0.0.1:~a" port)))
(n 0))
`(let ,(mapcar #'(lambda (p) `(,(mksymb "PORT-" (incf n)) ,p)) range)
(progn ,,@body)))) ;; one more comma here
But it doesn’t work:
(let ((start 1))
(with-free-ports start 3 port-1)) ;; error: port-1 is unbound
because with this ,,@body
I evaluate port-1: (progn ,port-1)
and this triggers the error.
I would appreciate if smbd can help me a bit and say what I am doing wrong.
Thank you.
If you compile code, then the compiler expands macro forms at compile-time. If you want to generate a variable number of let bindings, then the number needs to be known at compile-time.
start
andend
thus can’t be variables at run-time.If we compile above form, then at compile-time the value of
start
generally is unknown.Thank you for your reply.
Here is an example when the compiler does exactly that:
(defmacro with-free-ports (start end &body body) `(list ,start ,end ,@body)) (let ((start 1) (end 3)) (with-free-ports start end NIL))
So, why does it work?
No, the compiler does not do that. If you look into your macro here, it just puts the args start and end back in. The generated code then is executed. Remember: START and END are bound to the source forms, not computed values.
In the original macro you had the form
(loop for port from start to end ...)
, which you tried to compute at macro-expansion time. But the values of START and END are not necessary numbers, but source forms, like variable names.