Elisp:加载时的宏扩展,带有未定义变量的eval

时间:2016-09-07 19:38:38

标签: elisp emacs24 lisp-macros

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

提前致谢!

2 个答案:

答案 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)))

但请注意,语义有点不同:变量只能动态范围而不是词法范围,因为变量列表只能在运行时知道。