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

Ish*_*eck 4 lisp emacs

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

鉴于以下......

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

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

(makeplusser 'three)
Run Code Online (Sandbox Code Playgroud)

哪个让我这个......

(defun three+ (y) (+ (three) y))
Run Code Online (Sandbox Code Playgroud)

...我突出显示,点击Cj,并且能够使用......

(three+ 4) ; => 7
Run Code Online (Sandbox Code Playgroud)

我这样做的时候......

(eval (makeplusser 'four)) ; => four+
Run Code Online (Sandbox Code Playgroud)

它将函数名称" four + " 转储到缓冲区,这使我相信它已被正确地解析.当我尝试使用它时......

(four+ 3)
Run Code Online (Sandbox Code Playgroud)

我收到以下错误消息:

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

问题是:

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

ffe*_*tte 8

1-您的问题不是由eval-print-last-sexp和之间的差异引起的eval.

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

(setq exp (list 'defun 'four+ '(y) (list '+ '(four) 'y)))
(eval exp)
(four+ 4)
Run Code Online (Sandbox Code Playgroud)

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

(setq exp (makeplusser 'four))
(eval exp)
(four+ 4)
Run Code Online (Sandbox Code Playgroud)

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

2 -你所面对的实际问题与您为函数的符号的方式:make-symbol创建一个uninterned符号,该不会是从函数调用外部可见.您应该创建一个实习符号,如下所示:

(defun makeplusser (x)
  (list 'defun (intern (format "%s+" x)) '(y) 
    (list '+ (list x) 'y)))
(eval (makeplusser 'four))
Run Code Online (Sandbox Code Playgroud)

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

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

  • 宏不卫生,因为它绑定符号`y`,它可以被调用的函数引用,如`(four)`,因为Emacs Lisp是动态范围的.请参阅原始答案中的其他说明. (2认同)

Kaz*_*Kaz 6

免责声明:我不知道Emacs Lisp,但我知道Lisp.

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

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

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

(在Common Lisp中,来自的"uninterned symbol" (make-symbol "four")通常用哈希点表示法打印,就像#:four你可以发现它们一样.(实际上#:意味着没有家庭包装的符号,而不是未经处理的符号,但这是非常模糊的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
Run Code Online (Sandbox Code Playgroud)

也:

如果你想编写代码生成代码,你真的必须学习反引用.否则你是以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))))
Run Code Online (Sandbox Code Playgroud)

何时使用 make-symbol

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

;; 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))))
Run Code Online (Sandbox Code Playgroud)

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

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