Elisp:加载时使用未定义变量的 eval 进行宏扩展
Elisp: Macro expansion at load time with eval of undefined variable
这里是 Lisp 菜鸟。
我的目标是定义一个宏,使点分列表的键可以作为变量访问相应的值,因此得名 »let-dotted-alist«。所以我想要的是:
(setq foo '((a . "aa") (b . "bb")))
(let-dotted-alist foo
(message (concat a b)))
==> "aabb"
这是迄今为止我能想到的最好的:
(defmacro let-dotted-alist (alist &rest body)
"binds the car of each element of dotted ALIST to the corresponding cdr and makes them available as variables in BODY."
`(let ,(nreverse
(mapcar
(lambda (p) (list (car p) (cdr p)))
(eval alist)))
,@body))
这里的问题是eval
。我需要它以便能够将 alist 作为变量 (foo
) 而不是文字传递,这是定义函数时的主要用例。为了让宏计算出代码,这个变量需要已经绑定。仍然到处都是我读到的,使用 eval 往往表明代码中存在缺陷?有解决办法吗?
如果 Emacs 24 没有引入想要在加载时扩展宏的急切宏扩展,那么这将是一个有点学术性的问题,当变量(dotlist
在以下示例中)应该提供alist 仍然无效:
(defun concat-my-cdrs (dotlist)
(let-dotted-alist dotlist
(print (concat a b))))
根据我对此的评估方式,我要么得到 »mapcar:Symbol's value as variable is void: dotlist« 要么 »Eager macro-expansion failure: (void-variable dotlist)«。这当然是有道理的,因为变量 dotlist 在加载时确实是空的。
现在,在我尝试按照(本地)禁用急切宏扩展的思路找到解决方法之前,是否有任何方法可以改进宏定义以完全避免 eval
?
我不相信你可以避免 eval
,问题的陈述方式:macroexpand let
-bindings depending on a variable。不使用 eval
的唯一方法是将变量评估推迟到 macroexpand 之后,但这与扩展 let
绑定的要求相矛盾。我想我在说一些对你来说显而易见的事情。
所以问题显然是您的要求是否可以更改以消除 eval
的需要。这意味着要么避免 let
-绑定,要么在宏参数中明确绑定。是否可以接受由您决定,但我个人的看法是 - eval
并没有坏到扼杀您的用例。我会把 eval
放在那里。 (我最近在 Clojure 中做了基本相同的事情,我需要将相同的局部符号绑定到不同的值,模拟 OCaml 仿函数。这是一个题外话来解释为什么我可能偏向于保持你这样做的方式。)
我可能不知道一些特定于 elisp 的技巧 - 虽然即使那样我可能更喜欢 eval
而不是一些我迄今为止从未遇到过的技巧。
首先,我会提到 eager-macroexpansion 不会引入新问题:如果您尝试对文件进行字节编译,早期的 Emacsen 中也会出现同样的问题。
至于避免 eval
,你可以通过使用其他使用 eval
的东西,例如cl-progv
:
(defmacro let-dotted-alist (alist &rest body)
(macroexp-let2 nil alist alist
`(cl-progv (mapcar #'car ,alist)
(mapcar #'cdr ,alist)
,@body)))
但请注意语义有点不同:变量只能是动态作用域而不是词法作用域,因为变量列表仅在 运行 时间才知道。
这里是 Lisp 菜鸟。
我的目标是定义一个宏,使点分列表的键可以作为变量访问相应的值,因此得名 »let-dotted-alist«。所以我想要的是:
(setq foo '((a . "aa") (b . "bb")))
(let-dotted-alist foo
(message (concat a b)))
==> "aabb"
这是迄今为止我能想到的最好的:
(defmacro let-dotted-alist (alist &rest body)
"binds the car of each element of dotted ALIST to the corresponding cdr and makes them available as variables in BODY."
`(let ,(nreverse
(mapcar
(lambda (p) (list (car p) (cdr p)))
(eval alist)))
,@body))
这里的问题是eval
。我需要它以便能够将 alist 作为变量 (foo
) 而不是文字传递,这是定义函数时的主要用例。为了让宏计算出代码,这个变量需要已经绑定。仍然到处都是我读到的,使用 eval 往往表明代码中存在缺陷?有解决办法吗?
如果 Emacs 24 没有引入想要在加载时扩展宏的急切宏扩展,那么这将是一个有点学术性的问题,当变量(dotlist
在以下示例中)应该提供alist 仍然无效:
(defun concat-my-cdrs (dotlist)
(let-dotted-alist dotlist
(print (concat a b))))
根据我对此的评估方式,我要么得到 »mapcar:Symbol's value as variable is void: dotlist« 要么 »Eager macro-expansion failure: (void-variable dotlist)«。这当然是有道理的,因为变量 dotlist 在加载时确实是空的。
现在,在我尝试按照(本地)禁用急切宏扩展的思路找到解决方法之前,是否有任何方法可以改进宏定义以完全避免 eval
?
我不相信你可以避免 eval
,问题的陈述方式:macroexpand let
-bindings depending on a variable。不使用 eval
的唯一方法是将变量评估推迟到 macroexpand 之后,但这与扩展 let
绑定的要求相矛盾。我想我在说一些对你来说显而易见的事情。
所以问题显然是您的要求是否可以更改以消除 eval
的需要。这意味着要么避免 let
-绑定,要么在宏参数中明确绑定。是否可以接受由您决定,但我个人的看法是 - eval
并没有坏到扼杀您的用例。我会把 eval
放在那里。 (我最近在 Clojure 中做了基本相同的事情,我需要将相同的局部符号绑定到不同的值,模拟 OCaml 仿函数。这是一个题外话来解释为什么我可能偏向于保持你这样做的方式。)
我可能不知道一些特定于 elisp 的技巧 - 虽然即使那样我可能更喜欢 eval
而不是一些我迄今为止从未遇到过的技巧。
首先,我会提到 eager-macroexpansion 不会引入新问题:如果您尝试对文件进行字节编译,早期的 Emacsen 中也会出现同样的问题。
至于避免 eval
,你可以通过使用其他使用 eval
的东西,例如cl-progv
:
(defmacro let-dotted-alist (alist &rest body)
(macroexp-let2 nil alist alist
`(cl-progv (mapcar #'car ,alist)
(mapcar #'cdr ,alist)
,@body)))
但请注意语义有点不同:变量只能是动态作用域而不是词法作用域,因为变量列表仅在 运行 时间才知道。