为什么 Common-Lisp Lambda 表达式是有效的函数名称?

Why is a Common-Lisp Lambda expression a valid function name?

假设我想调用一些函数。如果我用 defun 定义了函数,我只是在列表的开头使用函数的名称,然后是它的参数(我将在示例中使用“=>”来显示输入代码的输出进入 CLisp REPL):

(defun f (a) (lambda (b) (+ a b))) => F
(f 12) => #<FUNCTION :LAMBDA (B) (+ A B)>

很简单。列表的第一个元素必须是函数、特殊运算符或宏的名称。除此之外,以下内容也有效:

((lambda (a) (+ a 12)) 1) => 13

快速 google 搜索表明 LAMBDA 既是一个符号又是一个宏。试图扩大宏观产量:

(macroexpand '(lambda (a) (+ a 12))) => #'(LAMBDA (A) (+ A 12))

这没有帮助。我无法区分宏 LAMBDA 和符号 LAMBDA,而且我完全不清楚为什么我可以使用 lambda 表达式作为函数名而不是 #'f, which, to my knowledge, should evaluate to a valid function-designator for the function F in the same way that #'(LAMBDA (A ) (+ A 12)) 确实而且还:

(#'f 12) => *** - EVAL: #'F is not a function name; try using a symbol instead

LAMBDA 是否是硬性规则的特殊例外,即计算表达式的第一个元素 必须 是某个操作的名称,或者是否有一些更一致的规则集我误会了吗?

Lambda 表达式和函数名称

lambda 表达式 不是函数名。 Common Lisp 中的函数名 被定义为symbols(setf symbol)lambda 表达式 基本上是描述匿名函数的内置语法。

请注意,lambda 表达式本身在 Common Lisp 中没有意义。它们仅以 lambda 形式(见下文)和带有特殊运算符 function 的形式出现。

以表格形式列出

Is LAMBDA a special exception to the otherwise hard-set rule that the first element of an evaluated expression must be the name of some operation, or is there some more consistent ruleset that I'm misunderstanding?

Common Lisp 规范定义只有四种基于列表的形式形式 是一段有效的 Lisp 代码。

  • 特殊形式(形式以特殊运算符开头)
  • 宏表格(表格以宏运算符开头)
  • 函数形式(形式以函数运算符开头)
  • lambda 形式(形式以 lambda 表达式开头)

参见 Common Lisp HyperSpec:Conses as Forms

请注意,Common Lisp 中没有对此进行扩展的机制。只有这四种类型的基于列表的表单。可以考虑扩展:数组作为函数,CLOS 对象作为函数,不同类型的函数,如 fexprs,变量,... None Common Lisp 语法支持基于列表的形式,并且没有可移植的添加这些的机制。

LAMBDA

LAMBDA 在 Common Lisp 中有两个不同的用途:

  • 它是 lambda 表达式 的头部。
  • 作为宏 LAMBDA。这会将 (lambda ....) 扩展为 (function (lambda ....))

LAMBDA 是在第一个语言定义 CLtL1 之后添加到 Common Lisp 中的,以便能够编写 (lambda (x) x) 而不是 (function (lambda (x) x))#'(lambda (x) x)。因此,它是函数特殊运算符形式的缩写,使代码看起来更简单,更像 Scheme-like.

可以想象发生以下翻译来评估函数(即汽车没有命名宏或特殊运算符)表达式:

(foo arg1 arg2 ...)
~~~>
(funcall (function foo) arg1 arg2 ...)

并将 function 理解为特殊运算符,它将表达式第一个元素中的内容转换为可以调用的实际函数运算符。 function 将 lambda 表达式(主要是在编译时)转换为可以调用的闭包。

最后请注意 #'foo 对于 (function foo) 是 shorthand。

这不是实际工作的方式,无论是在实践中还是在规范中,因为 (function #'(setf foo)) 是完全有效的并且可以评估为存在的函数,但 ((setf foo) arg1 arg2 ...) 不能是有效的函数调用.

然而,这确实解释了为什么像 (#'f x) 这样的表达式是无效的,原因是 (function (function f)) 不是一个有效的表达式。

我认为问题出在 lisp 打印机上,实现向我们展示了两者完全相同的东西,而在内部看来它确实是一个列表与一个函数。

常见且令人困惑的模式显示:

<i>[hao@wendy:~]$ ecl
;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/init.lsp"
n;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/ecl-readline.fas"
ix-;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/ecl-completions.fas"
sECL (Embeddable Common-Lisp) 16.1.3 (git:UNKNOWN)
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright (C) 2000 Juan J. Garcia-Ripoll
Copyright (C) 2016 Daniel Kochmanski
ECL is free software, and you are welcome to redistribute it
under certain conditions; see file 'Copyright' for details.
Type :h for Help.  
Top level in: #<process TOP-LEVEL>.
h+CL-USER[1]> (macroexpand '(lambda (a) (+a 12)))
#'(LAMBDA (A) (+A 12))
T
+CL-USER[2]> (equal (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12)))
NIL
+CL-USER[3]> (map 'list #'type-of (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(CONS CONS)
+CL-USER[4]> (map 'list #'functionp (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(NIL NIL)
+CL-USER[5]> (map 'list #'car (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
#'LAMBDA

似乎唯一的区别只是 hashquote?

+CL-USER[8]> (ignore-errors (list *print-escape* *print-readably* *print-case* *print-circle* *print-level* *print-length* *print-pretty*))
(T NIL :UPCASE NIL NIL NIL T)
+CL-USER[9]> (let ((*print-readably* t)) (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12)))))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
;;; Warning: Ignoring an unmatched right parenthesis.
+CL-USER[10]> (setq *print-pretty* nil)
NIL
+CL-USER[11]> (setq the-funs (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
+CL-USER[12]> *print-pretty* 
NIL

我试图强制打印机显式显示定义 [22] 的 (function (lambda ...)) 部分,但我不记得上次我做完全相同的事情试图找出为什么微妙刚开始获得对 lisp 的信任时出现

+CL-USER[21]> (type-of (car the-funs)))
CONS
;;; Warning: Ignoring an unmatched right parenthesis.
+CL-USER[22]> (car (car the-funs))
FUNCTION

我们实际看到的是一个 list/cons(下方)和一个函数(上方)

+CL-USER[23]> (values #1=(cadr the-funs) (type-of #1#))
(LAMBDA (A) (+ A 12))
CONS

也许区别不仅仅是视觉上的印刷字符;这是 SBCL:

<i>[hao@wendy:~]$ sbcl
This is SBCL 2.0.0.nixos, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (setq fs (list (macroexpand '(lambda (x) (1+ x))) '(lambda (x) (1+ x))))
; in: SETQ FS
;     (SETQ FS (LIST (MACROEXPAND '(LAMBDA # #)) '(LAMBDA (X) (1+ X))))
; 
; caught WARNING:
;   undefined variable: COMMON-LISP-USER::FS
; 
; compilation unit finished
;   Undefined variable:
;     FS
;   caught 1 WARNING condition
(#'(LAMBDA (X) (1+ X)) (LAMBDA (X) (1+ X)))

* (let (*print-pretty*) (print (lambda (x) (1+ x))))

#<FUNCTION (LAMBDA (X)) {52B1CABB}> 
#<FUNCTION (LAMBDA (X)) {52B1CABB}>
* (let (*print-pretty*) (print ' (lambda (x) (1+ x))))

(LAMBDA (X) (1+ X)) 
(LAMBDA (X) (1+ X))

这里的引述更明确

#<FUNCTION (LAMBDA (X)) {52B1CABB}> 
(LAMBDA (X) (1+ X))

树,类型和ECL说的有点相似

* (mapcar #'functionp fs) 
(NIL NIL)
* (mapcar #'type-of fs)
(CONS CONS)
* (mapcar #'car fs)
#'LAMBDA
* (values (car (car fs)) (car (cdr fs)))
FUNCTION
(LAMBDA (X) (1+ X))

最后,(不要采取)随机树遍历 穿​​过森林确认我们可以遍历列表,但函数只是 closed?

* (defun walk-tree (fun tree)
  (subst-if t
            (constantly nil)
            tree
            :key fun))
WALK-TREE
* (walk-tree 'print (lambda (x) (1+ x)))

#<FUNCTION (LAMBDA (X)) {52B1CD5B}> 
#<FUNCTION (LAMBDA (X)) {52B1CD5B}>
* (walk-tree 'print '(lambda (x) (1+ x))))

(LAMBDA (X) (1+ X)) 
LAMBDA 
((X) (1+ X)) 
(X) 
X 
NIL 
((1+ X)) 
(1+ X) 
1+ 
(X) 
X 
NIL 
NIL 
(LAMBDA (X) (1+ X))
* (defun walk-tree-atoms (fun tree)
  (tree-equal tree tree
              :test (lambda (element-1 element-2)
                      (declare (ignore element-2))
                      (funcall fun element-1)
                      t)))
WALK-TREE-ATOMS
* (walk-tree-atoms 'print (lambda (x) (1+ x)))

#<FUNCTION (LAMBDA (X)) {52B1CF9B}> 
T
* (walk-tree-atoms 'print '(lambda (x) (1+ x)))

LAMBDA 
X 
NIL 
1+ 
X 
NIL 
NIL 
T
* (quit)

<i>[hao@wendy:~]$ 

步行功能取自 LispTips,遗憾的是它似乎被删除了,我读到这里时突然想起了,你仍然可以在这里查看存档: https://web.archive.org/web/20191204131626/https://lisptips.com/post/43404489000/the-tree-walkers-of-cl

大家也可以看看整个session,我贴在gist里了: https://gist.github.com/LaloHao/fd6499b68cc98cf440aad6447ebd9b89