原谅我的冗长。我不确定如何描述我的情况。
鉴于以下内容......
(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))
问题是:
答案 0 :(得分: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)
虽然以相同的方式评估以下代码段不能按预期工作:
(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
并返回到对象four
。 eval
会看到来自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-symbol
比gensym
更常用。唯一符号在代码生成(宏)中非常有用,可以为必须对周围代码完全不可见的事物生成唯一标签,例如插入的代码块中的临时局部变量。你的函数的参数应该是一个独特的符号:
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,我们避免了这个问题;用户的代码不可能引用这个论点:我们已经实现了"卫生"或"透明度"。