Reputation: 32466
How can I expand a macro that adds symbols to the obarray
(here defun
) inside another loop macro like a dolist
? For example,
(defmacro make-cmd (cmd &optional search)
"Some function factory."
(let ((fn (intern (concat "fn-" cmd))))
`(defun ,fn (&optional args)
(interactive)
(let ((str (symbol-name ',fn))
,@(when search
'((dir "~"))))
(message "Called %S from %S" str
(or (and (bound-and-true-p dir) dir)
default-directory))))))
;; functions `fn-f1' and `fn-f2' aren't added to obarray
(dolist (x '("f1" "f2"))
`(make-cmd ,x t))
;; works like this
(make-cmd "f1" t)
I would like to be able to just require the macros when compiling and loop over function names. A common-lisp solution would probably be fine to adapt to emacs-lisp.
Upvotes: 2
Views: 411
Reputation: 30718
(Others have answered your direct question. But this might answer the question behind your question and speak to what you are really trying to do. If not, see the other answers.)
You do not need a macro at all, to do what you want. Just use defalias
or fset
. Each is a function.
(defun foo (cmd &optional search)
(let ((fn (intern (concat "fn-" cmd))))
(defalias fn `(lambda (&optional args)
(let ((str (symbol-name ',fn))
,@(when search
'((dir "~"))))
(message "Called %S from %S" str
(or (and (bound-and-true-p 'dir) dir)
default-directory)))))))
(dolist (x '("f1" "f2")) (foo x))
Then (symbol-function 'fn-f1)
returns:
(lambda (&optional args)
(let ((str (symbol-name 'fn-f1)))
(message "Called %S from %S" str (or (and (bound-and-true-p dir) dir)
default-directory))))
And if you use lexical binding (i.e., put the local-variable binding -*- lexical-binding: t -*-
at the top of the file where this code is defined, then you don't need any of the backquoting. For example:
(defun foo (cmd &optional search)
(let ((fn (intern (concat "fn-" cmd))))
(defalias fn (lambda (&optional args)
(let ((str (symbol-name fn))
(dir (if search "~" default-directory)))
(message "Called %S from %S" str dir))))))
(dolist (x '("f1" "f2")) (foo x))
If you do that then each function fn-f1
and fn-f2
is defined as a closure, which looks like this:
(symbol-function 'fn-f1)
(closure
((fn . fn-f1)
(search)
(cmd . "f1")
t)
(&optional args)
(let ((str (symbol-name fn))
(dir (if search "~" default-directory)))
(message "Called %S from %S" str dir)))
And (foo "f3" :SEARCH)
defines a function fn-f3
(a closure) that encapsulates a binding of otherwise free variable search
to the non-nil
value :SEARCH
, so that local variable dir
becomes bound to "~"
:
(symbol-function 'fn-f3)
(closure
((fn . fn-f3)
(search . :SEARCH) ;; <==============
(cmd . "f3")
t)
(&optional args)
(let ((str (symbol-name fn))
(dir (if search "~" default-directory)))
(message "Called %S from %S" str dir)))
Upvotes: 3
Reputation: 139411
Another typical solution in Common Lisp is to write a macro defcmds
so that you can write:
(defcmds ("f1" 1) ("f2" t))
Which one would make expand into:
(progn
(defcmd "f1" t)
(defcmd "f2" t))
Where thedefcmd
forms would expand into
(progn
(defun ...)
(defun ...))
Upvotes: 2
Reputation: 58647
You need:
(dolist (x '("f1" "f2"))
(eval `(make-cmd ,x t)))
The backquote expression `(make-cmd ,x t)
only constructs the syntax. That syntax isn't evaluated. It's the same thing as if you had written (list 'make-cmd x t)
.
If the primary use for this make-cmd
macro calls for eval
, it might as well be turned into a function:
(defun make-cmd-fun (cmd &optional search)
"Some function factory."
(let ((fn (intern (concat "fn-" cmd))))
`(defun
...)))
Now you have a plain function which returns defun
syntax; and that of course has to be eval
-ed to put it into effect. But now we don't have to construct any more syntax at the call site where we use the macro:
(dolist (x '("f1" "f2"))
;; Note how at least the backquote is gone (but not eval).
(eval (make-cmd-fun x t))) ;; execute the defun form returned by make-cmd-fun
When we have make-cmd-fun
, we can then define the macro version in terms of it:
(defmacro make-cmd (cmd &optional search)
(make-cmd-fun cmd search))
Basically we have made the raw macro expander function available as make-cmd-fun
; the macro make-cmd
just calls that expander.
Upvotes: 2