Lisp-rookie在这里。
我的目标是定义一个宏,它将使虚线alist的键可用作变量来访问相应的值,因此名称为“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没有引入渴望在加载时扩展宏的宏扩展,当应该提供alist的变量(以下示例中的dotlist
)时,这将是一个有点学术问题。仍无效:
(defun concat-my-cdrs (dotlist)
(let-dotted-alist dotlist
(print (concat a b))))
根据我对此的评价,我得到»mapcar:符号的值作为变量是void:dotlist«或»Eager宏扩展失败:( void-variable dotlist)«。这当然是有道理的,因为变量dotlist在加载时确实是空的。
在我尝试找到(本地)禁用急切宏扩展的解决方法之前,有没有办法改进宏定义以完全避免eval
?
提前致谢!
答案 0 :(得分:1)
我不相信你可以避免eval
,问题的陈述方式:macroexpand let
- 绑定取决于变量。不使用eval
的唯一方法是将变量评估推迟到宏扩展之后,但它与扩展let
绑定的要求相矛盾。我想我正在陈述一些对你来说很明显的事情。
所以问题显然是你的要求是否可以改变以消除eval
的需要。这意味着要么避免let
- 绑定,要么在宏参数中使绑定显式化。由你来决定它是否可以接受,但我个人的意见是 - eval
并不是那么糟糕,以至于杀死你的用例。我会保留eval
。 (我最近在Clojure中基本上做了同样的事情,我需要将相同的局部符号绑定到不同的值,模拟OCaml仿函数。这就是解释为什么我可能偏向于保持你的方式。)
我可能不知道一些特定于elisp的技巧 - 尽管即便如此,我可能更喜欢eval
到目前为止我从未遇到的一些诡计。
答案 1 :(得分:1)
首先,我要提到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)))
但请注意,语义有点不同:变量只能动态范围而不是词法范围,因为变量列表只能在运行时知道。