在SBCL中,如何将lambda表达式放入结构槽[例如(setf (struct-slot1 struct1) '(lambda (x) (* x x)))
],以便可以使用funcall
或apply
调用它? SBCL编译器抱怨:wanted one of (FUNCTION SYMBOL)
。有没有办法安装这样的lambda而不编译它或给它一个明确的名称?为什么 lambda表达式不是函数?我想做点什么:(funcall (function (struct-slot1 struct1)) 3)
。感谢您的任何见解。 (ps:通常我在运行之前编译lambda,但在调试期间我需要看到lambda的内部。)
答案 0 :(得分:1)
这不是特定于SBCL的,而是根据ANSI Common Lisp标准。
将 Lambda Expression 作为列表转换为函数
以下是您的选择:
CL-USER 168 > (funcall (coerce '(lambda (x) (* x x))
'function)
4)
16
CL-USER 169 > (funcall (compile nil '(lambda (x) (* x x)))
4)
16
CL-USER 170 > (funcall (eval '(lambda (x) (* x x))) ; because LAMBDA is a macro, too
4)
16
CL-USER 171 > (funcall (eval '(function (lambda (x) (* x x))))
4)
16
请注意,lambda表达式引用了 null词法环境。因此,它无法访问周围代码中的任何词汇变量。
Lambda表达式不是函数对象
为什么lambda表达式不是函数?
因为它只是一个列表,而不是代码。要将lambda表达式转换为代码,必须将其转换为函数对象。
其他一些(通常较旧的)Lisps允许您使用lambda表达式作为代码,但不能使用Common Lisp。这是由标准定义的。
你不能(funcall '(lambda (x) (+ x x)) 3)
。您必须首先将lambda表达式转换为函数对象。
FUNCTION是一个特殊的运算符 - >语法+语义强>
(funcall(function(struct-slot1 struct1))3)
这已经是语法错误了。 FUNCTION
是特殊运算符,需要函数名称或 lambda表达式。 (struct-slot1 struct1)
既不是。 (struct-slot1 struct1)
是从结构中检索值的代码。但它不是函数名称,它是符号或列表(setf <some-symbol>)
。它也不是 lambda表达式,它类似于(lambda (...) ...)
。
答案 1 :(得分:1)
(ps:通常我在运行之前编译lambda,但在调试期间我需要查看lambda的内部。)
试试这个:
(proclaim '(optimize (debug 3)))
在SBCL中,你也可以这样做:
(sb-ext:restrict-compiler-policy 'debug 3)
...建立允许调试的最低策略。 文档说:
另请参阅:WITH-COMPILATION-UNIT中的POLICY选项。
现在,为测试定义一个简单的哈希表:
CL-USER> (defparameter *hash* (make-hash-table :test #'eq))
插入匿名函数:
CL-USER> (setf (gethash :fun *hash*) (lambda (u) (* 10 u)))
#<FUNCTION (LAMBDA (U)) {1005140E2B}>
严重称呼:
CL-USER> (funcall (gethash :fun *hash*) "oops")
调试器显示:
Argument X is not a NUMBER: "oops"
[Condition of type SIMPLE-TYPE-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {10041C8033}>)
Backtrace:
0: (SB-KERNEL:TWO-ARG-* "oops" 10)
1: ((LAMBDA (U)) "oops")
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FUNCALL (GETHASH :FUN *HASH*) "oops") #<NULL-LEXENV>)
3: (EVAL (FUNCALL (GETHASH :FUN *HASH*) "oops"))
转到第1帧,((LAMBDA (U)) "oops")
,然后按 v (a.k.a。sldb-show-source
)。弹出一个新缓冲区,其中包含以下内容:
(LAMBDA ()
(PROGN
(LET* ((#:HASHTABLE *HASH*) (#:NEW1 (LAMBDA (U) (#:***HERE*** (* 10 U)))))
(SB-KERNEL:%PUTHASH :FUN #:HASHTABLE #:NEW1))))
由于调试器策略足够高,匿名函数的源代码可用,即使它是在REPL中键入的。如果您的匿名函数恰好是从源文件编译的,那么按 v 将转到该文件中的相应点(对应于上面***HERE***
术语中包含的表单)。
如果需要,您还可以在编译之前将代码存储为数据:
(defun wrap-and-compile (code)
(let ((function (compile nil code)))
(compile nil `(lambda (&rest args)
(restart-case (apply ,function args)
(DEBUG () :report ,(format nil "~S" code)
',code))))))
(setf (gethash :wrapped *hash*)
(wrap-and-compile '(lambda (x) (* 10 x))))
(funcall (gethash :wrapped *hash*) "Boo")
调试器显示:
Argument X is not a NUMBER: "Boo"
[Condition of type SIMPLE-TYPE-ERROR]
Restarts:
0: [DEBUG] (LAMBDA (X) (* 10 X))
1: [RETRY] Retry SLIME interactive evaluation request.
2: [*ABORT] Return to SLIME's top level.
3: [ABORT] abort thread (#<THREAD "worker" RUNNING {100513A403}>)
这有点hacky,但它确实有效。 DEBUG重新启动会返回编译函数的原始数据。