在 dolist 中扩展宏

Expanding macros inside dolist

如何在另一个循环宏(如 dolist 内)扩展一个向 obarray(此处为 defun)添加符号的宏?例如,

(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)

我希望在编译和遍历函数名时只需要宏。一个通用的 lisp 解决方案可能会很好地适应 emacs-lisp。

你需要:

(dolist (x '("f1" "f2"))
  (eval `(make-cmd ,x t)))

反引号表达式`(make-cmd ,x t)只构造语法。不评估该语法。这和你写 (list 'make-cmd x t).

是一样的

如果这个 make-cmd 宏的主要用途需要 eval,它还不如变成一个函数:

(defun make-cmd-fun (cmd &optional search)
  "Some function factory."
  (let ((fn (intern (concat "fn-" cmd))))
    `(defun 
       ...)))

现在你有一个简单的函数,它具有 returns defun 语法;当然必须 eval-ed 才能生效。但是现在我们不必在使用宏的调用站点构造任何语法:

(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

当我们有 make-cmd-fun 时,我们就可以根据它定义宏版本:

(defmacro make-cmd (cmd &optional search)
  (make-cmd-fun cmd search))

基本上我们已经使原始宏扩展器功能可用 make-cmd-fun;宏 make-cmd 只是调用那个扩展器。

Common Lisp 中另一个典型的解决方案是写一个宏defcmds 这样你就可以写:

(defcmds ("f1" 1) ("f2" t))

哪一个会扩展成:

(progn
  (defcmd "f1" t)
  (defcmd "f2" t))

其中defcmd 表格将扩展为

(progn
  (defun ...)
  (defun ...))

(其他人已经回答了你的直接问题。但这可能会回答你问题背后的问题并说明你真正想做的事情。如果没有,请参阅其他答案。)

不需要一个宏来做您想做的事。只需使用 defaliasfset。每个都是一个函数.

(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))

然后(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))))

并且如果您使用 词法绑定(即,将局部变量绑定 -*- lexical-binding: t -*- 放在定义此代码的文件顶部,那么您不需要不需要任何反引号。例如:

(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))

如果你这样做,那么每个函数 fn-f1fn-f2 都被定义为一个闭包,看起来像这样:

(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)))

(foo "f3" :SEARCH)定义了一个函数fn-f3(一个闭包),它封装了一个绑定,否则自由变量search到非nil:SEARCH, 这样局部变量 dir 就绑定到 "~":

(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)))