为什么#'在Common Lisp中的lambda之前使用?

Vít*_*újo 35 common-lisp

我想知道为什么我看到的大多数Common Lisp代码都有类似的东西

(mapcar #'(lambda (x) (* x x)) '(1 2 3))

而不仅仅是

(mapcar (lambda (x) (* x x)) '(1 2 3)),

这似乎也有效.我开始学习Common Lisp,并且在Scheme中有一些背景,这引起了我的兴趣.

编辑:我知道您需要带有函数名称的#',因为它们与变量位于不同的命名空间中.我的问题只是在lambda之前的#',因为lambda已经返回一个函数对象(我认为).#' - 由于宏扩展而减少lambdas的工作只会让它变得更有趣......

dan*_*lei 39

#'foo缩写(function foo)通过读取器.

在CL,有几种不同的命名空间,#'foo(function foo)将返回功能价值foo.

您可能想要搜索"Lisp-1 vs. Lisp-2",查看其他Stackoverflow问题,或阅读Pitman和Gabriel旧文章,以便了解更多关于多个命名空间(也称为插槽或符号单元格)的概念).

在lambda的情况下,#'可能在CL中省略的原因是它是一个宏,因此扩展(取自Hyperspec):

(lambda lambda-list [[declaration* | documentation]] form*)
==  (function (lambda lambda-list [[declaration* | documentation]] form*))
==  #'(lambda lambda-list [[declaration* | documentation]] form*)
Run Code Online (Sandbox Code Playgroud)

#'仍然可能出于历史原因使用(我认为在Maclisp lambda中没有扩展到函数形式),或者因为有些人认为用尖锐的引号标记lambda可能会使代码更具可读性或连贯性.可能会有一些特殊情况会产生影响,但总的来说,选择哪种形式并不重要.

我想你可以这样想:(function (lambda ...))返回函数(lambda ...)创建.请注意,lambda在CL中,Hyperspec具有宏和符号条目.从后者:

lambda表达式是一个列表,可以在某些上下文中用来代替函数名,通过直接描述其行为来表示函数,而不是通过引用已建立函数的名称来间接表示函数.

文件function:

如果name是lambda表达式,则返回词法闭包.

我认为差异也与调用这样的lambda形式有关:((lambda ...) ...)它被视为要评估的形式,与(funcall #'(lambda ...) ...).如果你想阅读更多关于这个主题,有一个关于它的cll线程.

该线程的一些引用:

(lambda (x) ...本身就是一些不带引号的列表结构.它作为FUNCTION特殊形式的参数出现(function (lambda (x) ...,导致函数对象存在

和:

事实上,LAMBDA宏是一个相当晚的ANSI Common Lisp,所以所有真正的老家伙(比如我)在你需要向lambda表达式提供#'时学会了他们的lisp.映射函数.否则将调用不存在的lambda函数.

宏的添加改变了这一点,但是我们中的一些人在我们想要改变的方式上也是如此.


Pab*_*dez 9

在大多数情况下最好避免使用#',因为它"大部分"都是不必要的,并使您的代码更加冗长.当需要某种形式的引用时有一些例外(见下面的例4).

注意:本文中的所有示例都已经在Emacs Lisp(GNU Emacs 25.2.1)中进行了测试,但它们在任何ANSI常见的lisp中都应该完全相同.两种方言的基本概念是相同的.

简单的解释
首先,让我们研究一个最好避免引用的案例.函数是第一类对象(例如,像任何其他对象一样处理,包括将它们传递给函数并将它们分配给变量的能力),它们自我评估.匿名函数(例如lambda形式)就是这样一个例子.在Emacs Lisp(Mx ielm RET)或任何ANSI常见的lisp上尝试以下操作.

((lambda (x) (+ x 10)) 20) -> 30
Run Code Online (Sandbox Code Playgroud)

现在,试试引用的版本

(#'(lambda (x) (+ x 10)) 20) -> "function error" or "invalid function..."  
Run Code Online (Sandbox Code Playgroud)

如果你坚持使用#',你必须写

(funcall #'(lambda (x) (+ x 10)) 20) -> 30
Run Code Online (Sandbox Code Playgroud)

详细说明
为了真正理解何时需要引用,必须知道Lisp如何评估表达式.继续阅读.我保证简明扼要.

您需要了解有关Lisp的一些基本事实:

  1. Lisp"总是" 评估每个表达式.好吧,除非引用表达式,否则它将被返回未评估.
  2. 原子评估自己.原子表达式不是列表.示例包括数字,字符串,哈希表和向量.
  3. 符号(变量名称)存储两种类型的值.它们可以包含常规值和功能定义.因此,Lisp符号有两个称为单元格的槽来存储这两种类型.非功能性内容通常保存在符号的值单元格中,并在功能单元格中起作用.同时保存非功能和功能定义的能力将Emacs Lisp和Common Lisp置于2-Lisp类别中.在表达式中使用两个单元格中的哪一个取决于符号的使用方式 - 更具体地说,它在列表中的位置.相比之下,Lisp的一些方言中的符号,即最着名的Scheme,只能包含一个值.Scheme没有价值和功能单元的概念.这样的Lisp统称为1-Lisps.

现在,您需要大致了解Lisp如何评估S表达式(括号表达式).每个S表达式的评估大致如下:

  1. 如果引用,则将其评为未评估
  2. 如果未引用,请获取其CAR(例如第一个元素)并使用以下规则对其进行评估:

    一个.如果是原子,只需返回其值(例如3 - > 3,"pablo" - >"pablo")
    b.如果是S表达式,则使用相同的整体过程
    c 来评估它.如果是符号,则返回其功能单元格的内容

  3. 评估S表达式的CDR中的每个元素(例如,除了列表的第一个元素之外的所有元素).

  4. 将从CAR获得的函数应用于从CDR中的每个元素获得的值.

上述过程意味着UNQUOTED S表达式的CAR中的任何符号必须在其功能单元中具有有效的功能定义.

现在,让我们回到帖子开头的例子.为什么

(#'(lambda (x) (+ x 10)) 20)  
Run Code Online (Sandbox Code Playgroud)

生成错误?这是因为#'(lambda(x)(+ x 10)),即S语言的CAR,由于函数引号#'而未被Lisp解释器评估.

#'(lambda (x) (+ x 10))
Run Code Online (Sandbox Code Playgroud)

不是一个功能,但是

(lambda (x) (+ x 10))
Run Code Online (Sandbox Code Playgroud)

是.请记住,引用的目的是为了防止评估.另一方面,lambda表单对自身进行求值,这是一种函数形式,它作为UNQUOTED列表的CAR有效.当Lisp评估CAR的时候

((lambda (x) (+ x 10)) 20)  
Run Code Online (Sandbox Code Playgroud)

它得到(lambda(x)(+ x 20)),这是一个可以应用于列表中其余参数的函数(假设CDR的长度等于lambda表达式允许的参数数量).因此,

((lambda (x) (+ x 10)) 20) -> 30  
Run Code Online (Sandbox Code Playgroud)

因此,问题是何时引用包含功能定义的函数或符号.除非你"错误地"做事,否则答案几乎绝不会."错误"是指你应该在符号的值单元格或功能单元格中放置功能定义.请参阅以下示例以获得更好的理解:

示例1 - 存储在值单元格中的函数
假设您需要对需要可变数量参数的函数使用"apply".一个这样的例子是符号+.Lisp将+视为常规符号.功能定义存储在+的功能单元中.如果您愿意,可以为其值单元格指定值

(setq + "I am the plus function").  
Run Code Online (Sandbox Code Playgroud)

如果你评价

+ -> "I am the plus function"
Run Code Online (Sandbox Code Playgroud)

但是,(+ 1 2)仍然按预期工作.

(+ 1 2) -> 3
Run Code Online (Sandbox Code Playgroud)

函数apply在递归中非常有用.假设您要对列表中的所有元素求和.你不能写

(+ '(1 2 3)) -> Wrong type...  
Run Code Online (Sandbox Code Playgroud)

原因是+期望它的参数是函数.apply解决了这个问题

(apply #'+ '(1 2 3)) -> (+ 1 2 3) -> 6  
Run Code Online (Sandbox Code Playgroud)

为什么我引用+以上?记住我上面概述的评估规则.Lisp通过检索存储在其功能单元中的值来评估符号应用.它获得了一个可以应用于参数列表的功能过程.但是,如果我不引用+,Lisp将检索存储在其值单元格中的值,因为它不是S表达式中的第一个元素.因为我们将+的值单元格设置为"我是加号函数",所以Lisp没有得到+函数单元格中的函数定义.实际上,如果我们没有将其值单元格设置为"我是加号函数",那么Lisp将检索nil,这不是一个函数,如apply所要求的那样.

有没有办法使用+ unquoted with apply.就在这里.您只需评估以下代码:

(setq + (symbol-function '+))  
(apply + '(1 2 3))  
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这将评估为6,因为当Lisp求值时(apply +'(1 2 3)),它现在找到存储在+的值单元格中的+的功能定义.

示例2 - 在值单元格中存储功能定义
假设您在符号的值单元格中存储功能定义.这实现如下:

(setq AFunc (lambda (x) (* 10 x)))
Run Code Online (Sandbox Code Playgroud)

评估

(AFunc 2)
Run Code Online (Sandbox Code Playgroud)

生成错误,因为Lisp无法在AFunc的函数单元格中找到函数.你可以通过使用funcall解决这个问题,它告诉Lisp使用符号值单元格中的值作为函数定义.您可以使用"funcall"执行此操作.

(funcall AFunc 2)
Run Code Online (Sandbox Code Playgroud)

假设存储在符号的值单元格中的功能定义有效,

(funcall AFunc 2) -> 20  
Run Code Online (Sandbox Code Playgroud)

您可以通过使用fset将lambda表单放在符号的函数单元格中来避免使用funcall:

(setf AFunc (lambda (x) (* 10 x)))  
(AFunc 2)  
Run Code Online (Sandbox Code Playgroud)

此代码块将返回20,因为lisp在AFunc的功能单元中找到功能定义.

示例3 - 本地函数
假设您正在编写函数,并且需要一个不会在其他任何地方使用的函数.典型的解决方案是定义仅在主要范围内有效的函数.试试这个:

(defun SquareNumberList (AListOfIntegers)
    "A silly function with an uncessary
   local function."
  (let ((Square (lambda (ANumber) (* ANumber ANumber))))
    (mapcar Square AListOfIntegers)
    )
  )  

(SquareNumberList '(1 2 3))  
Run Code Online (Sandbox Code Playgroud)

此代码块将返回

(1 4 9)  
Run Code Online (Sandbox Code Playgroud)

在上面的例子中没有引用Square的原因是根据上面概述的规则评估S表达式.首先,Lisp提出了'mapcar的功能定义.接下来,Lisp拉出其第二个参数(例如'Square)值单元格的内容.最后,它返回'(1 2 3)未评估的第三个参数.

例4 - 符号的值和功能单元的内容
这是一个需要引号的情况.

(setq ASymbol "Symbol's Value")  
(fset 'ASymbol (lambda () "Symbol's Function"))  
(progn  
  (print (format "Symbol's value -> %s" (symbol-value 'ASymbol)))  
  (print (format "Symbol's function -> %s" (symbol-function 'ASymbol)))
  )    
Run Code Online (Sandbox Code Playgroud)

上面的代码将评估为

"Symbol's value -> Symbol's Value"  
"Symbol's function -> (lambda nil Symbol's Function)"  
nil
Run Code Online (Sandbox Code Playgroud)

两者都需要报价

(fset 'ASymbol (lambda () "Symbol's Function"))  
Run Code Online (Sandbox Code Playgroud)

(symbol-value 'ASymbol)  
Run Code Online (Sandbox Code Playgroud)

(symbol-function 'ASymbol)  
Run Code Online (Sandbox Code Playgroud)

因为否则Lisp会在每种情况下获得ASymbol的值,从而防止fset,符号值和符号函数正常工作.

我希望这篇冗长的文章证明对某人有用.