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)评估((四加三)) 问题是:
免责声明:我不知道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))))