请原谅我的冗长.我不确定如何描述我的情况.
鉴于以下......
(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.
例如,如果您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)
免责声明:我不知道Emacs Lisp,但我知道Lisp.
我相信你遇到的是读取/打印与未加工符号的混淆.也就是说,我怀疑(make-symbol ...)在Emacs Lisp中,就像Common Lisp等其他方言中的同名函数一样,创建了一个新的符号对象,它与从打印中扫描的同名符号无关.符号(来自文件,终端,字符串,编辑缓冲区,......).
当您获取代码生成函数(也称为S表达式)的打印输出时,您的代码会起作用,因为符号的打印符号会four被读回到Lisp中并被插入,从而导致该符号成为与名称相同的对象功能four.
但是(make-symbol "four")是一个不同的符号对象.当您使用eval函数中的代码时,您直接使用数据结构,因此通过将代码减少到文本并将其读回来不会掩盖您的错误.符号不会转换为令牌four并four通过实习返回到对象.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".gensym比make-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,我们避免了这个问题; 用户的代码不可能引用参数:我们已经实现了"卫生"或"透明度".