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

emacs lisp-eval与eval print last sexp意外不同,emacs,lisp,Emacs,Lisp,请原谅我的长篇大论。我不知道该如何描述我的处境 鉴于以下情况 (defun three () 3) (defun four () 4) (defun makeplusser (x) (list 'defun (make-symbol (format "%s+" x)) '(y) (list '+ (list x) 'y))) 在我的scratch缓冲区中,我键入以下内容,然后点击C-j(eval print last sexp) 这让我觉得 (defun three+ (y) (

请原谅我的长篇大论。我不知道该如何描述我的处境

鉴于以下情况

(defun three () 3)
(defun four () 4)
(defun makeplusser (x)
  (list 'defun (make-symbol (format "%s+" x)) '(y) 
    (list '+ (list x) 'y)))
在我的scratch缓冲区中,我键入以下内容,然后点击C-j(eval print last sexp)

这让我觉得

(defun three+ (y) (+ (three) y))
。。。我突出显示,点击C-j,并且能够使用

(three+ 4) ; => 7
当我这么做的时候

(eval (makeplusser 'four)) ; => four+
它将函数名“four+”转储到缓冲区,这使我相信它已被正确地解除。当我尝试使用它时

(four+ 3)
我收到以下错误消息:

调试器已输入--Lisp错误:(无效函数4+(4+3)
评估((四加三))

问题是:

  • 为什么(eval print last sexp)的行为与使用(eval)的行为如此不同
  • 如何在不计算缓冲区中生成的defun的情况下获取“plusser”函数?我希望能够(mapcar#eval(mapcar#makeplusser(三四))或其他什么东西,但这并不是我所期望的工作方式
    免责声明:我不知道Emacs Lisp,但我知道Lisp

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

    当您获取代码生成函数的打印输出(也称为S表达式)时,代码就会工作,因为符号
    four
    的打印符号被读回Lisp并插入,导致该符号成为与函数名
    four
    相同的对象

    但是
    (使符号“四”)
    是不同的符号对象。当您对函数产生的代码使用
    eval
    时,您直接使用了数据结构,因此您的错误不会通过将代码缩减为文本并重新读取而被掩盖。符号不会通过interning转换为令牌
    four
    ,并返回到对象
    four
    eval
    将看到来自
    make symbol
    的原始符号:指向同一内存块的同一机器指针

    (在Common Lisp中,来自
    (make symbol“four”)
    的“unterned symbol”通常用散列点表示法打印,如
    :four
    ,以便您可以识别它们。(实际上
    表示没有主包的符号,不是unterned,但这是非常模糊的常见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
    
    也: 如果您想编写代码生成代码,您确实必须学习backquote。否则,你将以20世纪60年代的方式而不是现代70年代的方式进行:

    ;; 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方言也有一个名为
    gensym
    的函数,类似于
    make symbol
    ,但它也在名称后面附加了一个递增的数字戳,以便在同一上下文中出现多个“gensym”时更容易区分这些“gensym”
    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
    ,那么被调用的用户函数(如
    (四)
    可能包含对
    y
    的引用,程序员的意图是达到他或她自己的变量
    y
    )。但是您生成的函数意外地捕获了引用


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

    1-您的问题不是由
    eval print last sexp
    eval
    之间的差异造成的

    例如,如果您
    eval
    以下代码段(例如使用C-xc-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))))
    

    你的最后一个片段正是我想要的。我在宏上忙得不可开交,但始终无法正确地找到它。我认为你的例子让灯泡亮了起来。谢谢。更好的是
    (defmacro plusser(x)(makeplusser x))
    。如果已经有生成代码的函数,只需在宏中使用它。或者调用函数
    makeplusser-helper
    等,以及宏
    makeplusser
    。宏不卫生,因为它绑定了符号
    y
    ,可以被调用的函数引用,比如
    (四)
    ,因为Emacs Lisp是动态范围的。请参阅我原始答案中的其他注释。@Kaz你完全正确。我编辑了我的答案以反映这一点。感谢
    (defmacro makeplusser (x)
      (let ((name   (intern (format "%s+" x)))
            (argsym (make-symbol "arg")))
        `(defun ,name (,argsym)
           (+ (,x) ,argsym))))