SBCL 和 Lambda 表达式

SBCL & Lambda Expressions

在 SBCL 中,如何将 lambda 表达式放入结构槽 [例如,(setf (struct-slot1 struct1) '(lambda (x) (* x x)))],以便可以用 funcallapply 调用它? SBCL 编译器抱怨:wanted one of (FUNCTION SYMBOL)。有没有办法安装这样的 lambda 而无需编译它或给它一个明确的名称?为什么 lambda 表达式 不是函数?我想做类似的事情:(funcall (function (struct-slot1 struct1)) 3)。感谢您的任何见解。 (ps:通常我在 运行 之前编译 lambda,但在调试期间我需要查看 lambda 的内部结构。)

这不是 SBCL 特定的,而是根据 ANSI Common Lisp 标准。

Lambda 表达式 作为列表转换为函数

这些是您的选择:

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 表达式引用了 空词法环境 。因此它无法访问周围代码中的任何词法变量。

Lambda 表达式不是函数对象

Why is a lambda expression not a function?

因为它只是一个列表,而不是代码。要将 lambda 表达式转换为代码,您必须将其转换为函数对象。

其他一些(通常较旧的)Lisp 允许您将 lambda 表达式用作代码,但 Common Lisp 不允许。这是由标准定义的。

你做不到(funcall '(lambda (x) (+ x x)) 3)。您必须先将 lambda 表达式转换为函数对象。

FUNCTION是一个特殊的运算符->syntax+semantics

(funcall (function (struct-slot1 struct1)) 3)

这已经是语法错误了。 FUNCTION 是一个 特殊运算符 ,需要一个 函数名 或一个 lambda 表达式 (struct-slot1 struct1) 两者都不是。 (struct-slot1 struct1) 是从结构中检索值的代码。但它不是 函数名 ,而是 符号 或列表 (setf <some-symbol>)。它也不是 lambda 表达式,它类似于 (lambda (...) ...).

(ps: normally I compile the lambda before running, but during debugging I need to see the innards of the lambda.)

试试这个:

(proclaim '(optimize (debug 3)))

在SBCL中,您还可以这样做:

(sb-ext:restrict-compiler-policy 'debug 3)

... 建立最低允许的调试策略。 文档说:

See also :POLICY option in WITH-COMPILATION-UNIT.

现在,为测试定义一个简单的散列table:

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}>)

这有点老套,但它确实有效。 DEBUG 重新启动 returns 编译函数的原始数据。