emacs lisp - eval意外地与eval-print-last-sexp不同

时间:2012-03-21 13:53:34

标签: emacs lisp

原谅我的冗长。我不确定如何描述我的情况。

鉴于以下内容......

(defun three () 3)
(defun four () 4)
(defun makeplusser (x)
  (list 'defun (make-symbol (format "%s+" x)) '(y) 
    (list '+ (list x) 'y)))

在我的暂存缓冲区中,我输入以下内容然后点击C-j (eval-print-last-sexp) ......

(makeplusser 'three)

这让我知道了......

(defun three+ (y) (+ (three) y))

...我突出显示,点击C-j,并且能够使用......

(three+ 4) ; => 7

当我这样做时......

(eval (makeplusser 'four)) ; => four+

它将函数名称“四+ ”转储到缓冲区,这使我相信它已被正确地解除。当我尝试使用它时......

(four+ 3)

我收到以下错误消息:

  

调试器输入 - Lisp错误:( void-function four +)(4 + 3)
  eval((4 + 3))

问题是:

  1. 为什么(eval-print-last-sexp)与使用(eval)的行为完全不同?
  2. 如何在不必评估缓冲区中生成的defun的情况下获取“plusser”函​​数?我希望能够(mapcar#'eval(mapcar#'makeplusser'(三四)))或其他东西,但这不符合我的预期。

2 个答案:

答案 0 :(得分:8)

1 - 您的问题不是由eval-print-last-sexpeval之间的差异引起的。

例如,如果您eval以下代码段(例如使用 C-x C-e ),一切正常

(setq exp (list 'defun 'four+ '(y) (list '+ '(four) 'y)))
(eval exp)
(four+ 4)

虽然以相同的方式评估以下代码段不能按预期工作:

(setq exp (makeplusser 'four))
(eval exp)
(four+ 4)

即使exp的值在两种情况下都相同

2 - 您遇到的实际问题与您为函数创建符号的方式有关:make-symbol创建一个未处理的符号,该函数无法从函数外部看到呼叫。您应该创建一个实习符号,如下所示:

(defun makeplusser (x)
  (list 'defun (intern (format "%s+" x)) '(y) 
    (list '+ (list x) 'y)))
(eval (makeplusser 'four))

3 - 对于这样的事情,您应该考虑编写宏而不是评估函数的结果:

(defmacro makeplusser (x)
  (let ((name   (intern (format "%s+" x)))
        (argsym (make-symbol "arg")))
    `(defun ,name (,argsym)
       (+ (,x) ,argsym))))

答案 1 :(得分:6)

免责声明:我不了解Emacs Lisp,但我知道Lisp。

我相信你遇到的是读取/打印与未加工符号的混淆。也就是说,我怀疑Emacs Lisp中的(make-symbol ...),就像Common Lisp等其他方言中的同名函数一样,创建了一个新的符号对象,它与同名的符号无关。从打印的符号(从文件,终端,字符串,编辑缓冲区......)扫描。

当您获取代码生成函数(也称为S表达式)的打印输出时,您的代码有效,因为符号four的打印符号被读回到Lisp中并被实现,导致该符号成为与函数four的名称相同的对象。

但是(make-symbol "four")是一个不同的符号对象。当您对函数中出现的代码使用eval时,您直接使用数据结构,因此通过将代码缩减为文本并将其读回来不会掩盖您的错误。该符号不会通过实习转换为令牌four并返回到对象foureval会看到来自make-symbol的原始符号:指向同一块内存的同一台机器指针。

(在Common Lisp中,来自(make-symbol "four")的"未分隔符号"通常使用散列点表示法打印,例如#:four,因此您可以发现它们。(实际上{ {1}}表示没有家庭包装的符号,不是未经处理的符号,但这是非常模糊的Common Lisp微妙之处。))

无论如何,寻找一个名为#:的函数。 intern将查找具有该名称的现有符号并将其返回,而不是创建一个新符号。

(intern "four")

此外:

如果你想编写代码生成代码,你必须学习反引用。否则你是以1960年的方式而不是现代的1970年代的方式做到这一点:

;; two symbol interns for same name result in the same object
;; we are comparing the same pointer to itself
(eq (intern "foo") (intern "foo")) -> t

;; two symbol constructions result in two different object
;; two different pointers to separately allocated objects
(eq (make-symbol "foo") (make-symbol "foo")) -> nil

何时使用;; don't let your friends do this: (defun makeplusser (x) (list 'defun (make-symbol (format "%s+" x)) '(y) (list '+ (list x) 'y))) ;; teach them this: (defun makeplusser (x) `(defun ,(intern (format "%s+" x)) (y) (+ (,x) y))) ;; even more clearly, perhaps (defun makeplusser (func-to-call) (let ((func-name (format "%s+" x))) `(defun ,func-name (arg) (+ (,func-to-call) arg))))

当您需要创建有保证的唯一符号(与任何其他符号不同的对象)时,请使用make-symbol,即使它恰好与其他符号具有相同的名称。其他Lisp方言也有一个名为make-symbol的函数,就像gensym一样,但它也会在名称后附加一个递增的数字标记,以便更容易区分这些" gensyms"当多个在同一个上下文中出现时。在拥有它的Lisps中,make-symbolgensym更常用。唯一符号在代码生成(宏)中非常有用,可以为必须对周围代码完全不可见的事物生成唯一标签,例如插入的代码块中的临时局部变量。你的函数的参数应该是一个独特的符号:

make-symbol

原因是:Emacs Lisp是动态范围的。如果我们调用参数;; even more clearly, perhaps (defun makeplusser (func-to-call) (let ((func-name (format "%s+" x)) (arg-sym (make-symbol "arg")) `(defun ,func-name (,arg) (+ (,func-to-call) ,arg)))) ,则调用的用户函数(如y)可能包含对(four)的引用,其中程序员的意图是达到他或她自己的变量y。但是你生成的函数意外地捕获了引用!

通过对参数使用gensym,我们避免了这个问题;用户的代码不可能引用这个论点:我们已经实现了"卫生"或"透明度"。