首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Lisp (快板通用Lisp)如何在lambda中使用变量‘vs #’

Lisp (快板通用Lisp)如何在lambda中使用变量‘vs #’
EN

Stack Overflow用户
提问于 2020-10-11 20:33:41
回答 2查看 166关注 0票数 2

我希望有人能解释为什么测试1-5工作,但测试6不能。我认为引用带有“和在lambda前面使用#”的lambda都返回指向函数的指针,唯一的区别是#将首先编译它。

代码语言:javascript
运行
复制
(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。以下是产出:

代码语言:javascript
运行
复制
(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]
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-10-12 09:07:18

首先,您应该知道您的测试1-4符合Common,而测试5和6则不符合。我相信快板完全被允许做5和6的事情,但是它所做的是超出标准的。谈到这一点的标准是像mapcar这样以函数指示符为参数的函数的定义,以及功能指示符的定义。

函数指示符 n.函数的指示符;即表示函数的对象,它是:符号(表示该符号在全局环境中命名的函数)或函数(表示其本身)。如果将符号用作函数指示符,但它没有作为函数的全局定义,或者具有作为宏或特殊形式的全局定义,则后果是未定义的。..。

由此可以清楚地看出,像(lambda (...) ...)这样的列表并不是一个函数指示符:它只是一个恰好是lambda的列表。快板正在做的是注意到这个列表实际上是可以转换成一个函数的东西,并且可以这样做。

那么,让我们编写一个mapcar的版本,它可以像快板那样:

代码语言:javascript
运行
复制
(defun mapcar/coercing (maybe-f &rest lists)
  (apply #'mapcar (coerce maybe-f 'function) lists))

它只使用coerce,它是一个知道如何将这样的列表转换为函数的函数,还有其他一些东西。如果它的参数已经是一个函数,coerce只返回它。

现在,我们可以使用以下函数编写这两个测试:

代码语言:javascript
运行
复制
(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并不是动态范围的。

也许,让这一点更加清晰的一种方法是认识到,我们可以将函数创建从函数中直接提升出来:

代码语言:javascript
运行
复制
(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的重要之处在于它不是一个函数:它是一个非常神奇的东西,它告诉计算器(或编译器)它的参数是当前词法上下文中函数的描述,因此,例如在

代码语言:javascript
运行
复制
(let ((x 1))
  (function (lambda (y) (+ x y))))

它创建的函数所引用的x是由let绑定的x。因此,在您的测试2和4(它们是相同的):

代码语言:javascript
运行
复制
(defun test-4 (y)
  (mapcar (function (lambda (x) (expt x y)))
      '(1 2 3)))

所创建的函数所指的y的绑定是y的绑定,它在词汇上是可见的,是test-4本身的参数。

票数 6
EN

Stack Overflow用户

发布于 2020-10-12 08:02:19

让我们添加一个y参数,以避免关闭变量,并查看我们正在操作的值类型:

代码语言:javascript
运行
复制
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期间未绑定

代码语言:javascript
运行
复制
(let ((x 3)) (eval '(list x))) ;; ERROR

在这里,我们构建了一个由eval评估的let

代码语言:javascript
运行
复制
(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-5test-6中,没有函数,只有一个未计算的结构化表达式,可以解释为代码。

类型强制

现在,某些像MAPCAR这样的函数被用来应用函数。请注意规范是如何说明function参数是函数指示符的。

函数--一个函数的指示器,它的参数必须和列表中的参数一样多。

类型的指示符不一定是该类型的值,但可以是可以强制使用到该类型的值。有时,用户希望指定路径名并输入字符串,但字符串不是路径名类型的值:系统必须将字符串转换为路径名。

公共Lisp定义了一个COERCE函数,其中包含关于如何将值转换为其他值的规则。在您的例子中,mapcar首先做(coerce (lambda ...) 'function)。这方面的定义如下:

如果结果类型是函数,而对象是lambda表达式,则结果是空词法环境中对象的闭包。

因此,该值是在空词法环境中计算的,因此它无法访问周围的绑定;y是lambda表达式中的一个空闲变量,而且由于它是在空环境中计算的,所以它是未绑定的。这就是为什么test-5通过但test-6失败的原因。

名称解析、编译器和后期绑定

在引用函数#'f'f时,f是一个符号,两者有区别:在第一种情况下,将表达式计算为function类型的对象,而在第二种情况下,只计算符号。

此函数的名称解析可能会根据编译器的工作方式而改变。使用符号作为函数指示符,甚至不需要定义函数,当必须将符号强制作为函数时,名称将被解析。

编写#'f时,一些编译器可能会删除一个级别的间接,直接使代码跳转到与函数关联的代码,而不必在运行时解析名称。

但是,这也意味着使用此类编译器(例如SBCL),您需要重新编译函数重新定义上的一些调用站点,因为--如果函数是内联声明的,否则一些旧代码仍将引用#'f的先前定义。这在一开始并不是很重要的,但是当您正在进行实时编码时,这可能是一个困惑的来源。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64308742

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档