问题描述
我希望有人能解释为什么测试1-5起作用,而测试6却不起作用。我以为用lambda前面加上一个lambda并在其中使用#都会返回指向该函数的指针,唯一的区别是#'将首先编译它。
(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]
我正在使用Franz Industries Allegro Common Lisp的免费版本。以下是输出:
sum(unlist(Map(function(x,y) x[y],mydata[c("FTHG","FTAG")],list(mydata$HomeTeam=="Aston Villa",mydata$AwayTeam=="Aston Villa"))))
解决方法
首先,您应该知道测试1-4符合Common Lisp,而测试5和6则不符合。我相信Allegro完全可以执行5和6的工作,但是它的工作超出了标准。讨论这个问题的标准的一点是像mapcar
这样的函数的定义,该函数以函数指示符作为参数,以及function designator的定义:
功能指示符 n 。 功能的指示符;也就是说,一个对象代表一个函数,并且是以下之一:符号(表示在全局环境中由该符号命名的函数)或 function (表示本身)。如果将符号用作功能指示符,但它没有作为函数的全局定义,或者具有作为宏或特殊形式的全局定义,则后果是不确定的。 [...]
由此可以清楚地看出,像(lambda (...) ...)
这样的列表不是功能指示符:它只是一个列表,其汽车恰好是lambda
。 Allegro正在做的事情是注意到该列表实际上是可以转化为函数并可以执行此操作的东西。
好吧,让我们写一个mapcar
的版本,该版本可以执行Allegro的操作:
(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 Lisp(除了特殊变量外)是词法范围的。 词法范围只是一种奇特的说法,即可用的绑定(变量)是准确的,只有您可以通过查看程序源看到的绑定。 (除非是CL的特殊绑定,我将忽略,因为这里没有。)
因此,考虑到这一点,请考虑test-6/coercing
,尤其是对mapcar/coercing
的调用:在该调用中,coerce
必须将列表(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
是受x
约束的let
。因此,在您的测试2和4(相同)中:
(defun test-4 (y)
(mapcar (function (lambda (x) (expt x y)))
'(1 2 3)))
创建的函数所引用的y
的绑定是y
的绑定,该绑定在词汇上是可见的,这是test-4
本身的参数。
让我们添加一个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-like 形式被评估为函数,而第三个形式被评估为cons单元。就Lisp而言,第三个参数只是一棵没有意义的符号树。
阅读器宏
我以为用lambda引一个lambda并在其前面使用#'都会返回该函数的指针,唯一的区别是#'将首先编译它。
让我们回到定义,'
和#'
是阅读器宏,分别是Single-Quote和Sharpsign Single-Quote。它们位于其他形式的前面,例如'f
读为(quote f)
,而#'f
读为(function f)
。在读取时,f
和生成的表格只是未评估的数据。
我们将在下面看到两个特殊运算符的解释方式,但真正重要的是词法范围,所以我们打开一个括号。
词汇环境
Lexical environments是在代码某些点有效的绑定集。当您评估let
或flet
时,它将通过新的绑定丰富当前环境。在表达式上调用EVAL
时,即使对eval
本身的调用是在非空环境中,您也将从空词法环境开始求值。
x
在eval
期间未绑定:
(let ((x 3)) (eval '(list x))) ;; ERROR
在这里,我们构建一个让eval
进行评估的租赁交易:
(eval '(let ((x 3)) (list x)))
=> (3)
这就是词汇环境下的速成课程。
特殊运算符
功能特殊运算符FUNCTION
接受一个参数,该参数可以是函数的名称(符号或setf)或 lambda表达式;特别是:
function的值是当前词汇环境中name的功能值。
此处,lambda表达式是在当前词法环境中求值的,这意味着它可以引用lambda表达式之外的变量。那就是闭包的定义,它们捕获周围的绑定。
NB。您无需在lambda
前面加上#'
,因为存在一个名为(lambda ...)
的宏,它可以将扩展扩展到(function (lambda ...))
中。看起来这可能会永远递归扩展,但事实并非如此:首先扩展宏,使(lambda ...)
变为(function (lambda ...))
,然后特殊运算符function
知道如何求值lambda表达式本身。
这意味着(lambda ...)
和#'(lambda ...)
是等效的。特别要注意的是,此时并没有编译一种形式,编译器在宏扩展后将看到相同的表达式。
特殊运算符QUOTE
将(quote f)
评估为f
,其中f
本身未评估。在test-5
和test-6
中,没有函数,只是一个可以被解释为代码的未经评估的结构化表达式。
强制类型
现在,某些功能,例如MAPCAR
用于应用功能。请注意,规范说明function
参数是一个函数 designator :
function ---函数的代号,必须使用与列表一样多的参数。
类型的指定符不一定是该类型的值,而可以是可以强制转换为该类型的值。有时用户想要指定路径名,然后输入一个字符串,但是字符串不是pathname类型的值:系统必须将字符串转换为路径名。
Common Lisp使用有关如何将值转换为其他值的规则定义了COERCE
函数。对于您而言,mapcar
首先执行(coerce (lambda ...) 'function)
。定义如下:
如果result-type是function,而object是lambda表达式,则结果是null词法环境中object的闭包。
因此,该值是在 null词法环境中求值的,因此它无法访问周围的绑定; y
是您的lambda表达式中的一个自由变量,由于它是在空环境中求值的,因此它是不受约束的。这就是test-5
通过但test-6
失败的原因。
名称解析,编译器和后期绑定
在引用函数#'f
时写'f
或f
是有区别的,其中f
是符号:在第一种情况下,表达式的计算结果为类型为function
的对象,在第二种情况下,您仅评估符号。
此函数的名称解析可以根据编译器及其工作方式而改变。使用符号作为功能指示符,甚至不需要定义功能,在必须将符号强制作为功能时解析名称。
编写#'f
时,某些编译器可能会删除一个间接级别,而直接使您的代码跳转到与该函数关联的代码,而不必在运行时解析名称。
但是,这也意味着,使用此类编译器(例如SBCL),您需要在函数重定义上重新编译某些调用站点,就像该函数被声明为 inline 一样,否则某些旧代码仍然会请参考#'f
的先前定义。刚开始时,这并不是必须要考虑的重要内容,但是在进行实时编码时,可能要引起混淆。