【问题标题】:emacs lisp - eval unexpectedly different than eval-print-last-sexpemacs lisp - eval 出乎意料地不同于 eval-print-last-sexp
【发布时间】:2012-03-21 13:53:33
【问题描述】:

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

鉴于以下...

(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+”转储到缓冲区,这让我相信它已被正确地取消。当我尝试使用它时...

(four+ 3)

我收到以下错误消息:

调试器进入--Lisp 错误: (void-functionfour+) (four+ 3)
eval((四+ 3))

问题是:

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

【问题讨论】:

    标签: emacs lisp


    【解决方案1】:

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

    例如,如果你eval下面的sn-p(例如使用C-x C-e),一切正常

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

    而以相同的方式评估以下 sn-p 不能按预期工作:

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

    【讨论】:

    • 你最后的sn-p正是我想要的。我对宏大惊小怪,但永远无法正确地摸索到它。我认为你的例子只是让灯泡亮了。谢谢。
    • 更好(defmacro plusser (x) (makeplusser x))。如果您已经有了生成代码的函数,只需在宏中使用它即可。或者调用函数makeplusser-helper等,宏makeplusser
    • 宏不卫生,因为它绑定了符号y,该符号可以被调用的函数引用,例如(four),因为Emacs Lisp 是动态作用域的。请参阅我的原始答案中的其他说明。
    • @Kaz 你是绝对正确的。我编辑了我的答案以反映这一点。谢谢。
    • 第二句中的C-x e应该是C-x C-e
    【解决方案2】:

    免责声明:我不知道 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") 的“uninterned symbol”通常用散列点表示法打印,例如 #:four 以便您可以发现它们。(实际上 #: 表示没有 home 包的符号,不是没有实习,但这是非常模糊的 Common Lisp 微妙之处。))

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

    ;; 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
    

    还有:

    如果你想编写代码生成代码,你真的必须学习反引号。否则,您将采用 1960 年代的方式,而不是现代 1970 年代的方式:

    ;; 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

    当您需要创建一个保证唯一的符号(与任何其他符号不同的对象)时,请使用make-symbol,即使它恰好与其他符号同名。其他 Lisp 方言也有一个名为 gensym 的函数,它类似于 make-symbol,但它还在名称上附加了一个递增的数字标记,以便在同一上下文中出现多个“gensyms”时更容易区分这些“gensyms”。 gensym 在拥有它的 Lisp 中比 make-symbol 更常用。唯一符号在代码生成(宏)中很有用,用于为周围代码绝对不可见的事物生成唯一标签,例如插入的代码块中的临时局部变量。你的函数的参数应该是一个唯一的符号:

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

    原因是:Emacs Lisp 是动态作用域的。如果我们调用参数y,则存在被调用的用户函数(如(four))可能包含对y 的引用的风险,程序员的意图是访问他或她自己的变量y。但是您生成的函数意外捕获了引用!

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

    【讨论】:

    • @Kaz 出于好奇,如果不是 Emacs,你会用什么来编辑你的 lisp?
    • 这篇文章也很有帮助。 掌声
    猜你喜欢
    • 2017-08-19
    • 2012-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多