我希望有人能解释为什么测试1-5工作,但测试6不能。我认为引用带有“和在lambda前面使用#”的lambda都返回指向函数的指针,唯一的区别是#将首先编译它。
(defun test-1 (y)
(mapcar (lambda (x) (expt x 2))
'(1 2 3)))
(defun test-2 (y)
(mapcar (lambda (x) (expt x y))
'(1 2 3)))
(defun test-3 (y)
(mapcar #'(lambda (x) (expt x 2))
'(1 2 3)))
(defun test-4 (y)
(mapcar #'(lambda (x) (expt x y))
'(1 2 3)))
(defun test-5 (y)
(mapcar '(lambda (x) (expt x 2))
'(1 2 3)))
(defun test-6 (y)
(mapcar '(lambda (x) (expt x y))
'(1 2 3)))
我使用的自由版本的弗兰兹工业快板通用Lisp。以下是产出:
(test-1 2) ; --> (1 4 9)
(test-2 2) ; --> (1 4 9)
(test-3 2) ; --> (1 4 9)
(test-4 2) ; --> (1 4 9)
(test-5 2) ; --> (1 4 9)
(test-6 2) ; --> Error: Attempt to take the value of the unbound variable `Y'. [condition type: UNBOUND-VARIABLE]
发布于 2020-10-12 09:07:18
首先,您应该知道您的测试1-4符合Common,而测试5和6则不符合。我相信快板完全被允许做5和6的事情,但是它所做的是超出标准的。谈到这一点的标准是像mapcar
这样以函数指示符为参数的函数的定义,以及功能指示符的定义。
函数指示符 n.函数的指示符;即表示函数的对象,它是:符号(表示该符号在全局环境中命名的函数)或函数(表示其本身)。如果将符号用作函数指示符,但它没有作为函数的全局定义,或者具有作为宏或特殊形式的全局定义,则后果是未定义的。..。
由此可以清楚地看出,像(lambda (...) ...)
这样的列表并不是一个函数指示符:它只是一个恰好是lambda
的列表。快板正在做的是注意到这个列表实际上是可以转换成一个函数的东西,并且可以这样做。
那么,让我们编写一个mapcar
的版本,它可以像快板那样:
(defun mapcar/coercing (maybe-f &rest lists)
(apply #'mapcar (coerce maybe-f 'function) lists))
它只使用coerce
,它是一个知道如何将这样的列表转换为函数的函数,还有其他一些东西。如果它的参数已经是一个函数,coerce
只返回它。
现在,我们可以使用以下函数编写这两个测试:
(defun test-5/coercing (y)
(mapcar/coercing '(lambda (x) (expt x 2))
'(1 2 3)))
(defun test-6/coercing (y)
(mapcar/coercing '(lambda (x) (expt x y))
'(1 2 3)))
那么,在序言之后,为什么test-6/explicit
不能工作呢?答案是,Common在词汇范围内(除了特殊变量之外)。词汇作用域只是一种奇特的方式,它可以表示可用的绑定(变量)正是并且只有通过查看程序的源代码才能看到绑定。(但对于用于特殊绑定的CL,我将忽略它,因为这里没有。)
因此,考虑到这一点,请考虑test-6/coercing
,特别是对mapcar/coercing
的调用:在该调用中,coerce
必须将list (lambda (x) (expt z y))
转换为函数。所以就这么做了。但是它返回的函数不绑定y
,其中也没有y
的绑定:该函数使用y
'free‘。
如果coerce
为我们构建的函数是动态地为y
寻找绑定,那么这是唯一可行的方法。这就是动态作用域语言所做的,但CL并不是动态范围的。
也许,让这一点更加清晰的一种方法是认识到,我们可以将函数创建从函数中直接提升出来:
(defun test-7 (y f)
(mapcar f '(1 2 3)))
> (test-7 1 (coerce '(lambda (x) (expt x y)) 'function))
很明显,这在词汇范围内的语言中是行不通的。
那么,测试1-4是如何工作的呢?
首先,这里实际上只有两个测试。在CL中,lambda
是一个宏,而(lambda (...) ...)
完全等同于(function (lambda (...) ...))
。当然,#'(lambda (...) ...)
和(function (lambda (...) ...))
也是一样的:它只是一个读取宏。
(function ...)
是一种神奇的东西(一种特殊的形式),上面写着“这是一个函数”。function
的重要之处在于它不是一个函数:它是一个非常神奇的东西,它告诉计算器(或编译器)它的参数是当前词法上下文中函数的描述,因此,例如在
(let ((x 1))
(function (lambda (y) (+ x y))))
它创建的函数所引用的x
是由let
绑定的x
。因此,在您的测试2和4(它们是相同的):
(defun test-4 (y)
(mapcar (function (lambda (x) (expt x y)))
'(1 2 3)))
所创建的函数所指的y
的绑定是y
的绑定,它在词汇上是可见的,是test-4
本身的参数。
发布于 2020-10-12 08:02:19
让我们添加一个y
参数,以避免关闭变量,并查看我们正在操作的值类型:
USER> (type-of #'(lambda (x y) (expt x y)))
FUNCTION
USER> (type-of (lambda (x y) (expt x y)))
FUNCTION
USER> (type-of '(lambda (x y) (expt x y)))
CONS
如您所见,两个第一个lambda类表单被计算为函数,而第三个被计算为反单元格。对于Lisp来说,第三个论点只是一棵没有意义的符号树。
读取器宏
我认为引用带有“和在lambda前面使用#”的lambda都返回指向函数的指针,唯一的区别是#将首先编译它。
让我们回到定义,'
和#'
是读取器宏,分别是单引号和尖兵单曲。它们是在其他表单前面找到的,例如,'f
被读取为(quote f)
,#'f
被读取为(function f)
。在读取时,f
和生成的表单只是未评估的数据.
下面我们将看到两个特殊运算符是如何解释的,但真正重要的是词法范围,所以我们打开一个括号。
词汇环境
词汇环境是在代码的某一点上有效的绑定集合。当您评估一个let
或一个flet
时,它用新的绑定丰富了当前环境。当对表达式调用EVAL
时,即使对eval
本身的调用是在非空环境中,也可以从空词法环境开始计算。
在这里,x
只是在eval
期间未绑定
(let ((x 3)) (eval '(list x))) ;; ERROR
在这里,我们构建了一个由eval
评估的let
(eval '(let ((x 3)) (list x)))
=> (3)
这就是词汇环境速成课的全部内容。
特殊运算符
函数
特殊运算符FUNCTION
接受一个参数,该参数要么是函数的名称(符号或符号),要么是lambda表达式;特别是:
函数的值是当前词法环境中名称的函数值。
这里,lambda表达式是在当前的词法环境中计算的,这意味着它可以引用lambda表达式之外的变量。这就是闭包的定义,它们捕获了周围的绑定。
注意:您不需要在lambda
前面加上#'
,因为有一个名为(lambda ...)
的宏可以扩展为(function (lambda ...))
。看起来这可能会永远递归地展开,但事实并非如此:首先扩展宏,使(lambda ...)
变为(function (lambda ...))
,然后特殊运算符function
知道如何计算lambda表达式本身。这意味着(lambda ...)
和#'(lambda ...)
是等价的。特别要注意的是,在此时是否编译了一个表单,编译器将在宏展开后看到相同的表达式。
报价
特殊运算符QUOTE
将(quote f)
计算为f
,其中f
本身未计算。在test-5
和test-6
中,没有函数,只有一个未计算的结构化表达式,可以解释为代码。
类型强制
现在,某些像MAPCAR
这样的函数被用来应用函数。请注意规范是如何说明function
参数是函数指示符的。
函数--一个函数的指示器,它的参数必须和列表中的参数一样多。
类型的指示符不一定是该类型的值,但可以是可以强制使用到该类型的值。有时,用户希望指定路径名并输入字符串,但字符串不是路径名类型的值:系统必须将字符串转换为路径名。
公共Lisp定义了一个COERCE
函数,其中包含关于如何将值转换为其他值的规则。在您的例子中,mapcar
首先做(coerce (lambda ...) 'function)
。这方面的定义如下:
如果结果类型是函数,而对象是lambda表达式,则结果是空词法环境中对象的闭包。
因此,该值是在空词法环境中计算的,因此它无法访问周围的绑定;y
是lambda表达式中的一个空闲变量,而且由于它是在空环境中计算的,所以它是未绑定的。这就是为什么test-5
通过但test-6
失败的原因。
名称解析、编译器和后期绑定
在引用函数#'f
或'f
时,f
是一个符号,两者有区别:在第一种情况下,将表达式计算为function
类型的对象,而在第二种情况下,只计算符号。
此函数的名称解析可能会根据编译器的工作方式而改变。使用符号作为函数指示符,甚至不需要定义函数,当必须将符号强制作为函数时,名称将被解析。
编写#'f
时,一些编译器可能会删除一个级别的间接,直接使代码跳转到与函数关联的代码,而不必在运行时解析名称。
但是,这也意味着使用此类编译器(例如SBCL),您需要重新编译函数重新定义上的一些调用站点,因为--如果函数是内联声明的,否则一些旧代码仍将引用#'f
的先前定义。这在一开始并不是很重要的,但是当您正在进行实时编码时,这可能是一个困惑的来源。
https://stackoverflow.com/questions/64308742
复制相似问题