当存储在函数单元格中时,Lisp 将函数更改为 lambda 表达式
Lisp changes function to lambda expression when stored in function cell
在post中,我切题地问为什么我在SBCL
中声明
(defun a (&rest x)
x)
然后检查函数单元格包含的内容
(describe 'a)
COMMON-LISP-USER::A
[symbol]
A names a compiled function:
Lambda-list: (&REST X)
Derived type: (FUNCTION * (VALUES LIST &OPTIONAL))
Source form:
(LAMBDA (&REST X) (BLOCK A X))
我看到了原始功能的这个特殊细分。有人能解释一下这个输出是什么意思吗?我对最后一行感到特别困惑
Source form:
(LAMBDA (&REST X) (BLOCK A X))
这很神秘,因为出于某种我不清楚的原因,Lisp 已将原始函数转换为 lambda 表达式。如果知道像这样分解的函数是如何被调用的细节也很好。这个例子是 SBCL。在 Elisp 中
(symbol-function 'a)
给予
(lambda (&rest x) x)
再次,奇怪。正如我在另一个 post 中所说的那样,这在 Scheme 中更容易理解——但这在答案中造成了混淆。所以我再一次问,为什么 Lisp 采用普通函数声明并将其存储为 lambda 表达式?
DEFUN 是定义宏。宏转换代码。
在普通 Lisp 中:
(defun foo (a)
(+ a 42))
以上是定义形式,但会被DEFUN转成其他代码
效果类似
(setf (symbol-function 'foo)
(lambda (a)
(block foo
(+ a 42))))
以上将符号FOO的函数单元格设置为一个函数。 BLOCK 构造由 SBCL 添加,因为在 Common Lisp 中由 DEFUN 定义的命名函数创建一个与函数名称同名的 BLOCK。然后 RETURN-FROM 可以使用此块名称从特定函数启用非本地 return。
此外,DEFUN 还执行特定于实现的事情。实现中还记录了开发信息:源代码、定义位置等
方案有定义:
(define (foo a)
(+ a 10))
这会将 FOO 设置为函数对象。
我还是有点不清楚你在困惑什么,但这里试图解释一下。我会坚持使用 CL(主要是 ANSI CL),因为 elisp 有很多历史上的怪事,这只会让事情变得难以理解(elisp 有一个附录)。 Pre-ANSI CL 在很多事情上也不太清楚。
我会尝试通过 编写 一个宏来解释事情,它是 defun
的简单版本:我称之为 defun/simple
,并且它的一个使用示例是
(defun/simple foo (x)
(+ x x))
所以我需要做的是弄清楚这个宏的扩展应该是什么,以便它做一些大致相同的事情(但比更简单)defun
。
函数命名空间 & fdefinition
首先,我假设您对以下观点感到满意:在 CL(和 elisp)中,函数的命名空间不同于变量绑定的命名空间:两种语言都是 lisp-2。所以在像 (f x)
这样的形式中, f
是在函数绑定的命名空间中查找的,而 x
是在变量绑定的命名空间中查找的。这意味着像
这样的形式
(let ((sin 0.0))
(sin sin))
在 CL 或 elisp 中很好,而在 Scheme 中它们会出错,因为 0.0
不是函数,因为 Scheme 是 lisp-1。
所以我们需要某种方式来访问该名称空间,在 CL 中最通用的方式是 fdefinition
:(fdefinition <function name>)
获取 <function name>
的函数定义,其中 <function name>
是命名函数的东西,就我们的目的而言,它将是一个符号。
fdefinition
是 CL 调用的 accessor:这意味着 setf
macro 知道如何处理它,因此我们可以改变函数通过 (setf (fdefinition ...) ...)
绑定一个符号。 (这不是真的:我们可以使用 fdefinition
访问和改变的是符号的 顶级 函数绑定,我们 不能 访问或改变词法函数绑定,CL 没有提供这样做的方法,但这在这里并不重要。)
所以这告诉我们宏扩展需要是什么样子:我们想要将名称的(顶级)定义设置为某个函数对象。宏的展开应该是这样的:
(defun/simple foo (x)
x)
应该扩展到涉及
的东西
(setf (fdefinition 'foo) <form which makes a function>)
所以我们现在可以写这个宏了:
(defmacro defun/simple (name arglist &body forms)
`(progn
(setf (fdefinition ',name)
,(make-function-form name arglist forms))
',name))
这是这个宏的完整定义。它在其展开中使用 progn
,以便展开它的结果是被定义的函数的名称,这与 defun
相同:展开通过副作用完成所有实际工作。
但是 defun/simple
依赖于一个辅助函数,叫做 make-function-form
,我还没有定义它,所以你实际上不能 使用 它还.
函数形式
所以现在我们需要写make-function-form
。这个函数在宏展开时被调用:它的工作不是创建一个函数:它是return一点源代码,它将创建一个函数,我正在调用一个 'function form'.
那么,CL 中的函数形式是什么样的?好吧,在可移植 CL 中实际上只有一种这样的形式(这可能是错误的,但我认为是真的),这是一种使用特殊运算符 function
构造的形式。所以我们需要 return 一些看起来像 (function ...)
的表格。那么,...
可以是什么? function
.
有两种情况
(function <name>)
表示在当前词法环境中由<name>
命名的函数。所以 (function car)
就是我们说 (car x)
. 时调用的函数
(function (lambda ...))
表示(lambda ...)
指定的函数:a lambda expression.
第二个是仅(如上警告)我们可以构建表示新函数的形式的方式。所以 make-function-form
将需要 return 这第二种 function
形式。
所以我们可以写一个make-function-form
的初始版本:
(defun make-function-form (name arglist forms)
(declare (ignore name))
`(function (lambda ,arglist ,@forms)))
这足以让 defun/simple
工作:
> (defun/simple plus/2 (a b)
(+ a b))
plus/2
> (plus/2 1 2)
3
但还不太对:defun
定义的函数可以做的一件事是 return 来自他们自己:他们知道自己的名字并且可以使用 return-from
来return 来自它:
> (defun silly (x)
(return-from silly 3)
(explode-the-world x))
silly
> (silly 'yes)
3
defun/simple
还不能这样做。为此,make-function-form
需要在函数体周围插入一个合适的 block
:
(defun make-function-form (name arglist forms)
`(function (lambda ,arglist
(block ,name
,@forms))))
现在:
> (defun/simple silly (x)
(return-from silly 3)
(explode-the-world x))
silly
> (silly 'yes)
3
一切都很好。
这是defun/simple
及其辅助函数的最终定义。
查看 defun/simple
的扩展
我们可以用 macroexpand
以通常的方式做到这一点:
> (macroexpand '(defun/simple foo (x) x))
(progn
(setf (fdefinition 'foo)
#'(lambda (x)
(block foo
x)))
'foo)
t
这里唯一令人困惑的是,因为 (function ...)
在源代码中很常见,所以它有语法糖 #'...
:这与 quote
的原因相同有特殊语法。
真正的 defun
形式的宏扩展值得一看:它们通常有一堆特定于实现的东西,但你可以在那里找到同样的东西。这是来自 LW 的示例:
> (macroexpand '(defun foo (x) x))
(compiler-let ((dspec::*location* '(:inside (defun foo) :listener)))
(compiler::top-level-form-name (defun foo)
(dspec:install-defun 'foo
(dspec:location)
#'(lambda (x)
(declare (system::source-level
#<eq Hash Table{0} 42101FCD5B>))
(declare (lambda-name foo))
x))))
t
好吧,这里有很多额外的东西,而且 LW 显然有一些关于这个 (declare (lambda-name ...))
形式的技巧,可以让 return-from
在没有显式块的情况下工作。但是你可以看到基本上是一样的事情在发生。
结论:你是如何制作函数的
总而言之:像 defun
这样的宏或任何其他函数定义形式需要扩展为一种形式,在计算时将构造一个函数。 CL 恰好提供了一种这样的形式:(function (lambda ...))
: 这就是您在 CL 中创建函数的方式。所以像 defun
这样的东西必然要扩展成这样。 (准确地说:defun
的任何可移植版本:实现在某种程度上可以自由地执行实现魔法,并且可以这样做。但是它们 不能 自由地添加新的特殊运算符.)
当您调用 describe
时,您看到的是,在 SBCL 编译您的函数后,它会记住源格式是什么,并且源格式正是您从 defun/simple
此处给出宏。
备注
lambda
作为宏
在 ANSI CL 中,lambda
被定义为一个宏,其扩展是一个合适的 (function (lambda ...))
形式:
> (macroexpand '(lambda (x) x))
#'(lambda (x) x)
t
> (car (macroexpand '(lambda (x) x)))
function
这意味着您不必自己编写 (function (lambda ...))
:您可以依靠 lambda
的宏定义为您完成。从历史上看,lambda
在 CL 中并不总是一个宏:我找不到我的 CLtL1 副本,但我很确定它没有被定义为一个。我有理由相信 lambda
的宏定义已经到来,因此可以在 CL 之上编写与 ISLisp 兼容的程序。它必须在语言中,因为 lambda
在 CL
包中,因此用户不能为其定义可移植的宏(尽管他们经常定义这样的宏,或者至少我是这样) .我没有依赖上面这个宏定义。
defun/simple
并不是 defun
的正确克隆:它的唯一目的是展示如何编写这样的宏。特别是它没有正确处理声明,我认为:它们需要从 block
中移除,但不是。
Elisp
Elisp比CL恐怖多了。特别是,在 CL 中有一个定义明确的 function
类型,它与列表不相交:
> (typep '(lambda ()) 'function)
nil
> (typep '(lambda ()) 'list)
t
> (typep (function (lambda ())) 'function)
t
> (typep (function (lambda ())) 'list)
nil
(请特别注意,(function (lambda ()))
是一个函数,而不是一个列表:function
正在完成它创建函数的工作。)
然而,在 elisp 中,一个解释函数 只是一个列表,其 car 是 lambda
(警告:如果词法绑定在这不是这种情况:它是然后是汽车为 closure
的列表)。所以在 elisp 中(没有词法绑定):
ELISP> (function (lambda (x) x))
(lambda (x)
x)
和
ELISP> (defun foo (x) x)
foo
ELISP> (symbol-function 'foo)
(lambda (x)
x)
然后 elisp 解释器将按照您自己的方式解释这个列表。 function
在 elisp 中几乎与 quote
.
相同
但是 function
与 elisp 中的 quote
不完全相同:字节编译器知道,当遇到像 (function (lambda ...))
这样的形式时,这是一个函数形式,它应该字节编译主体。所以,我们可以看看defun
在elisp中的展开:
ELISP> (macroexpand '(defun foo (x) x))
(defalias 'foo
#'(lambda (x)
x))
(原来defalias
是现在最原始的东西。)
但是如果我将这个定义放在一个文件中,然后进行字节编译和加载,那么:
ELISP> (symbol-function 'foo)
#[(x)
"7"
[x]
1]
你可以进一步探索这个:如果你把 this 放在一个文件中:
(fset 'foo '(lambda (x) x))
然后字节编译加载,然后
ELISP> (symbol-function 'foo)
(lambda (x)
x)
所以字节编译器没有对 foo
做任何事情,因为它没有得到它应该做的提示。但是 foo
仍然是一个很好的函数:
ELISP> (foo 1)
1 (#o1, #x1, ?\C-a)
只是没有编译。这也是为什么如果在其中编写带有匿名函数的 elisp 代码,您应该使用 function
(或等效的 #'
)。 (最后,当然,如果启用词法作用域,(function ...)
会做正确的事情。)
在 CL 中创建函数的其他方法
最后,我在上面说过 function
并且特别是 (function (lambda ...))
是在 CL 中创建新函数的唯一原始方法。我不完全确定这是真的,尤其是考虑到 CLOS(几乎所有 CLOS 都会有某种 class 实例,它们是函数,但可以被 subclassed)。但这没关系:这是 a 方式就足够了。
在
(defun a (&rest x)
x)
然后检查函数单元格包含的内容
(describe 'a)
COMMON-LISP-USER::A
[symbol]
A names a compiled function:
Lambda-list: (&REST X)
Derived type: (FUNCTION * (VALUES LIST &OPTIONAL))
Source form:
(LAMBDA (&REST X) (BLOCK A X))
我看到了原始功能的这个特殊细分。有人能解释一下这个输出是什么意思吗?我对最后一行感到特别困惑
Source form:
(LAMBDA (&REST X) (BLOCK A X))
这很神秘,因为出于某种我不清楚的原因,Lisp 已将原始函数转换为 lambda 表达式。如果知道像这样分解的函数是如何被调用的细节也很好。这个例子是 SBCL。在 Elisp 中
(symbol-function 'a)
给予
(lambda (&rest x) x)
再次,奇怪。正如我在另一个 post 中所说的那样,这在 Scheme 中更容易理解——但这在答案中造成了混淆。所以我再一次问,为什么 Lisp 采用普通函数声明并将其存储为 lambda 表达式?
DEFUN 是定义宏。宏转换代码。
在普通 Lisp 中:
(defun foo (a)
(+ a 42))
以上是定义形式,但会被DEFUN转成其他代码
效果类似
(setf (symbol-function 'foo)
(lambda (a)
(block foo
(+ a 42))))
以上将符号FOO的函数单元格设置为一个函数。 BLOCK 构造由 SBCL 添加,因为在 Common Lisp 中由 DEFUN 定义的命名函数创建一个与函数名称同名的 BLOCK。然后 RETURN-FROM 可以使用此块名称从特定函数启用非本地 return。
此外,DEFUN 还执行特定于实现的事情。实现中还记录了开发信息:源代码、定义位置等
方案有定义:
(define (foo a)
(+ a 10))
这会将 FOO 设置为函数对象。
我还是有点不清楚你在困惑什么,但这里试图解释一下。我会坚持使用 CL(主要是 ANSI CL),因为 elisp 有很多历史上的怪事,这只会让事情变得难以理解(elisp 有一个附录)。 Pre-ANSI CL 在很多事情上也不太清楚。
我会尝试通过 编写 一个宏来解释事情,它是 defun
的简单版本:我称之为 defun/simple
,并且它的一个使用示例是
(defun/simple foo (x)
(+ x x))
所以我需要做的是弄清楚这个宏的扩展应该是什么,以便它做一些大致相同的事情(但比更简单)defun
。
函数命名空间 & fdefinition
首先,我假设您对以下观点感到满意:在 CL(和 elisp)中,函数的命名空间不同于变量绑定的命名空间:两种语言都是 lisp-2。所以在像 (f x)
这样的形式中, f
是在函数绑定的命名空间中查找的,而 x
是在变量绑定的命名空间中查找的。这意味着像
(let ((sin 0.0))
(sin sin))
在 CL 或 elisp 中很好,而在 Scheme 中它们会出错,因为 0.0
不是函数,因为 Scheme 是 lisp-1。
所以我们需要某种方式来访问该名称空间,在 CL 中最通用的方式是 fdefinition
:(fdefinition <function name>)
获取 <function name>
的函数定义,其中 <function name>
是命名函数的东西,就我们的目的而言,它将是一个符号。
fdefinition
是 CL 调用的 accessor:这意味着 setf
macro 知道如何处理它,因此我们可以改变函数通过 (setf (fdefinition ...) ...)
绑定一个符号。 (这不是真的:我们可以使用 fdefinition
访问和改变的是符号的 顶级 函数绑定,我们 不能 访问或改变词法函数绑定,CL 没有提供这样做的方法,但这在这里并不重要。)
所以这告诉我们宏扩展需要是什么样子:我们想要将名称的(顶级)定义设置为某个函数对象。宏的展开应该是这样的:
(defun/simple foo (x)
x)
应该扩展到涉及
的东西(setf (fdefinition 'foo) <form which makes a function>)
所以我们现在可以写这个宏了:
(defmacro defun/simple (name arglist &body forms)
`(progn
(setf (fdefinition ',name)
,(make-function-form name arglist forms))
',name))
这是这个宏的完整定义。它在其展开中使用 progn
,以便展开它的结果是被定义的函数的名称,这与 defun
相同:展开通过副作用完成所有实际工作。
但是 defun/simple
依赖于一个辅助函数,叫做 make-function-form
,我还没有定义它,所以你实际上不能 使用 它还.
函数形式
所以现在我们需要写make-function-form
。这个函数在宏展开时被调用:它的工作不是创建一个函数:它是return一点源代码,它将创建一个函数,我正在调用一个 'function form'.
那么,CL 中的函数形式是什么样的?好吧,在可移植 CL 中实际上只有一种这样的形式(这可能是错误的,但我认为是真的),这是一种使用特殊运算符 function
构造的形式。所以我们需要 return 一些看起来像 (function ...)
的表格。那么,...
可以是什么? function
.
(function <name>)
表示在当前词法环境中由<name>
命名的函数。所以(function car)
就是我们说(car x)
. 时调用的函数
(function (lambda ...))
表示(lambda ...)
指定的函数:a lambda expression.
第二个是仅(如上警告)我们可以构建表示新函数的形式的方式。所以 make-function-form
将需要 return 这第二种 function
形式。
所以我们可以写一个make-function-form
的初始版本:
(defun make-function-form (name arglist forms)
(declare (ignore name))
`(function (lambda ,arglist ,@forms)))
这足以让 defun/simple
工作:
> (defun/simple plus/2 (a b)
(+ a b))
plus/2
> (plus/2 1 2)
3
但还不太对:defun
定义的函数可以做的一件事是 return 来自他们自己:他们知道自己的名字并且可以使用 return-from
来return 来自它:
> (defun silly (x)
(return-from silly 3)
(explode-the-world x))
silly
> (silly 'yes)
3
defun/simple
还不能这样做。为此,make-function-form
需要在函数体周围插入一个合适的 block
:
(defun make-function-form (name arglist forms)
`(function (lambda ,arglist
(block ,name
,@forms))))
现在:
> (defun/simple silly (x)
(return-from silly 3)
(explode-the-world x))
silly
> (silly 'yes)
3
一切都很好。
这是defun/simple
及其辅助函数的最终定义。
查看 defun/simple
的扩展
我们可以用 macroexpand
以通常的方式做到这一点:
> (macroexpand '(defun/simple foo (x) x))
(progn
(setf (fdefinition 'foo)
#'(lambda (x)
(block foo
x)))
'foo)
t
这里唯一令人困惑的是,因为 (function ...)
在源代码中很常见,所以它有语法糖 #'...
:这与 quote
的原因相同有特殊语法。
真正的 defun
形式的宏扩展值得一看:它们通常有一堆特定于实现的东西,但你可以在那里找到同样的东西。这是来自 LW 的示例:
> (macroexpand '(defun foo (x) x))
(compiler-let ((dspec::*location* '(:inside (defun foo) :listener)))
(compiler::top-level-form-name (defun foo)
(dspec:install-defun 'foo
(dspec:location)
#'(lambda (x)
(declare (system::source-level
#<eq Hash Table{0} 42101FCD5B>))
(declare (lambda-name foo))
x))))
t
好吧,这里有很多额外的东西,而且 LW 显然有一些关于这个 (declare (lambda-name ...))
形式的技巧,可以让 return-from
在没有显式块的情况下工作。但是你可以看到基本上是一样的事情在发生。
结论:你是如何制作函数的
总而言之:像 defun
这样的宏或任何其他函数定义形式需要扩展为一种形式,在计算时将构造一个函数。 CL 恰好提供了一种这样的形式:(function (lambda ...))
: 这就是您在 CL 中创建函数的方式。所以像 defun
这样的东西必然要扩展成这样。 (准确地说:defun
的任何可移植版本:实现在某种程度上可以自由地执行实现魔法,并且可以这样做。但是它们 不能 自由地添加新的特殊运算符.)
当您调用 describe
时,您看到的是,在 SBCL 编译您的函数后,它会记住源格式是什么,并且源格式正是您从 defun/simple
此处给出宏。
备注
lambda
作为宏
在 ANSI CL 中,lambda
被定义为一个宏,其扩展是一个合适的 (function (lambda ...))
形式:
> (macroexpand '(lambda (x) x))
#'(lambda (x) x)
t
> (car (macroexpand '(lambda (x) x)))
function
这意味着您不必自己编写 (function (lambda ...))
:您可以依靠 lambda
的宏定义为您完成。从历史上看,lambda
在 CL 中并不总是一个宏:我找不到我的 CLtL1 副本,但我很确定它没有被定义为一个。我有理由相信 lambda
的宏定义已经到来,因此可以在 CL 之上编写与 ISLisp 兼容的程序。它必须在语言中,因为 lambda
在 CL
包中,因此用户不能为其定义可移植的宏(尽管他们经常定义这样的宏,或者至少我是这样) .我没有依赖上面这个宏定义。
defun/simple
并不是 defun
的正确克隆:它的唯一目的是展示如何编写这样的宏。特别是它没有正确处理声明,我认为:它们需要从 block
中移除,但不是。
Elisp
Elisp比CL恐怖多了。特别是,在 CL 中有一个定义明确的 function
类型,它与列表不相交:
> (typep '(lambda ()) 'function)
nil
> (typep '(lambda ()) 'list)
t
> (typep (function (lambda ())) 'function)
t
> (typep (function (lambda ())) 'list)
nil
(请特别注意,(function (lambda ()))
是一个函数,而不是一个列表:function
正在完成它创建函数的工作。)
然而,在 elisp 中,一个解释函数 只是一个列表,其 car 是 lambda
(警告:如果词法绑定在这不是这种情况:它是然后是汽车为 closure
的列表)。所以在 elisp 中(没有词法绑定):
ELISP> (function (lambda (x) x))
(lambda (x)
x)
和
ELISP> (defun foo (x) x)
foo
ELISP> (symbol-function 'foo)
(lambda (x)
x)
然后 elisp 解释器将按照您自己的方式解释这个列表。 function
在 elisp 中几乎与 quote
.
但是 function
与 elisp 中的 quote
不完全相同:字节编译器知道,当遇到像 (function (lambda ...))
这样的形式时,这是一个函数形式,它应该字节编译主体。所以,我们可以看看defun
在elisp中的展开:
ELISP> (macroexpand '(defun foo (x) x))
(defalias 'foo
#'(lambda (x)
x))
(原来defalias
是现在最原始的东西。)
但是如果我将这个定义放在一个文件中,然后进行字节编译和加载,那么:
ELISP> (symbol-function 'foo)
#[(x)
"7"
[x]
1]
你可以进一步探索这个:如果你把 this 放在一个文件中:
(fset 'foo '(lambda (x) x))
然后字节编译加载,然后
ELISP> (symbol-function 'foo)
(lambda (x)
x)
所以字节编译器没有对 foo
做任何事情,因为它没有得到它应该做的提示。但是 foo
仍然是一个很好的函数:
ELISP> (foo 1)
1 (#o1, #x1, ?\C-a)
只是没有编译。这也是为什么如果在其中编写带有匿名函数的 elisp 代码,您应该使用 function
(或等效的 #'
)。 (最后,当然,如果启用词法作用域,(function ...)
会做正确的事情。)
在 CL 中创建函数的其他方法
最后,我在上面说过 function
并且特别是 (function (lambda ...))
是在 CL 中创建新函数的唯一原始方法。我不完全确定这是真的,尤其是考虑到 CLOS(几乎所有 CLOS 都会有某种 class 实例,它们是函数,但可以被 subclassed)。但这没关系:这是 a 方式就足够了。