SBCL& Lambda表达式

时间:2016-10-13 21:50:30

标签: lambda common-lisp sbcl

在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的内部。)

2 个答案:

答案 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重新启动会返回编译函数的原始数据。