2010-07-24 23 views
33

我想知道为什么大多数的Common Lisp代码,我看到有东西像为什么#在Common Lisp的lambda之前使用?

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

,而不是仅仅

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

这似乎正常工作。我开始学习Common Lisp,并且在Scheme中有一些背景,这让我很感兴趣。

编辑:我知道你需要#'与函数名称,因为他们住在不同的变量名称空间。我的问题是关于lambda之前的#,因为lambda已经返回一个函数对象(我认为)。事实上,由于宏扩展而工作的事实只是使其更加耐人寻味......

回答

36

#'foo是对于(function foo)由读者。

在CL,有几种不同的命名空间,#'foo(function foo)将返回函数值的foo

您可能要search for "Lisp-1 vs. Lisp-2",检查其他Stackoverflow questions,或为了更多地了解多个命名空间的概念读取old article py Pitman and Gabriel(也称为插槽细胞符号)。

,在拉姆达的#'的情况下,也可以省略其原因是,这是在CL的宏,其扩展了正是如此(从Hyperspec截取):

(lambda lambda-list [[declaration* | documentation]] form*) 
== (function (lambda lambda-list [[declaration* | documentation]] form*)) 
== #'(lambda lambda-list [[declaration* | documentation]] form*) 

#'仍然可以使用由于历史原因(我认为在Maclisp lambda中没有扩展到函数形式),或者因为有些人认为,使用尖括号“标记”lambda可能会使代码更具可读性或连贯性。可能有一些特殊情况会导致这种差异,但总的来说,选择哪种形式并不重要。

我猜你可以这样想:(function (lambda ...))返回函数(lambda ...)创建。请注意,CL Hyperspec中的lambda同时有a macro AND a symbol entry。从后者:

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

functiondocumentation

如果名字是λ表达式,则 词法闭合被返回。

我觉得差异也与调用拉姆达形式是这样的:它被视为一种形式((lambda ...) ...)进行评估,对(funcall #'(lambda ...) ...)。如果您想要了解更多关于这个主题的信息,请参阅c.l.l thread

从该线程的一些话:

(lambda (x) ...本身只是一些 不带引号的表结构。这是它的 外观作为参数传递给 功能特殊形式(function (lambda (x) ...引起 函数对象存在

和:

它也通过这样的事实加剧是 的LAMBDA宏是而后期 除了ANSI Common Lisp,所以所有 的真正老家伙(即像我一样) 了解到他们的lisp,当你需要 提供#'到lambda表达式 在映射函数中。否则 将调用不存在的lambda函数 。

宏的添加改变了这一点,但 我们有些人在我们的方式设置太 想改变。

+1

我知道命名空间的区别。但是我期望,因为lambda直接返回一个函数对象(或者是它),所以不需要调用'function'或#'。为什么这样? – 2010-07-24 15:16:25

+0

更新了答案。 – danlei 2010-07-24 15:53:01

+0

嗯,所以不,纯lambda不返回一个函数对象...谢谢你的解释。 – 2010-07-24 16:35:55

5

在大多数情况下最好避免#因为它“大部分”是不必要的,并且使得代码更加冗长。有些情况下需要引用某种形式的引用(请参见下面的示例4)。

注:本文中的所有示例都已在Emacs Lisp(GNU Emacs 25.2.1)中进行了测试,但它们应该在任何ANSI通用lisp中以相同的方式工作。两种方言的基本概念是相同的。

简单的解释
首先,让我们研究的情况下的时候,最好避免引用。函数是第一类对象(例如像任何其他对象一样对待,包括将它们传递给函数并将它们分配给变量的能力),这些对象会评估自己。匿名函数(例如lambda表单)就是这样一个例子。在Emacs Lisp(M-x ielm RET)或任何ANSI通用lisp上尝试以下内容。

((lambda (x) (+ x 10)) 20) -> 30 

现在,尝试引用的版本

(#'(lambda (x) (+ x 10)) 20) -> "function error" or "invalid function..." 

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

(funcall #'(lambda (x) (+ x 10)) 20) -> 30 

详细解释
要真正当理解引用是必需的,必须知道Lisp如何评估表达式。请继续阅读。我保证做这个简洁。

您需要了解Lisp的几个基本事实:

  1. Lisp的 “永远” 计算每一个表情。那么,除非表达是引用的,在这种情况下,它将返回未评估。
  2. 原子评价自己。原子表达式不是列表。例子包括数字,字符串,哈希表和向量。
  3. 符号(变量名称)存储两种类型的值。他们可以保持常规值和功能定义。因此,Lisp符号有两个称为单元格的槽来存储这两种类型。非功能性内容通常保存在符号的值单元格中,并在函数单元格中运行。同时持有非功能性和功能性定义的能力将Emacs Lisp和Common Lisp放在2-Lisp类别中。在表达式中使用两个单元格中的哪一个取决于如何使用该符号 - 更具体地说是它在列表中的位置。相比之下,Lisp的一些方言中的符号,Scheme是最有名的,它只能保存一个值。 Scheme没有价值和功能细胞的概念。这样的Lisp统称为1-Lisp。

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

  1. 如果被引用,返回它不计算
  2. 如果不带引号的,得到它的CAR(例如,第一个元素),并使用以下规则评价它:

    一。如果原子只是返回它的值(例如3→3,“pablo” - >“pablo”)
    b。如果是S表达式,则使用相同的整体程序对其进行评估。如果符号返回其功能单元的内容,则返回S表达式的CDR中的每个元素(例如,除列表的第一个元素外的所有元素)。

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

上面的过程意味着,在一个未加引号 S-表达CAR任何符号必须在其功能细胞有效的功能性定义。

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

(#'(lambda (x) (+ x 10)) 20) 

产生错误?它会这样做,因为#'(lambda(x)(+ x 10))是S表达式的CAR,由于功能引用#',Lisp解释器不对其进行评估。

#'(lambda (x) (+ x 10)) 

不是一个函数,而是

(lambda (x) (+ x 10)) 

是。请记住,引用的目的是为了防止评估。另一方面,拉姆达形式评估自己,一个功能形式,它作为一个有效的列表的CAR。当Lisp的评估的

((lambda (x) (+ x 10)) 20) 

的CAR它得到(拉姆达(X)(+×20)),其是可以在一个列表被应用到的参数的其余部分的功能(提供的长度CDR等于lambda表达式允许的参数数量)。因此,

((lambda (x) (+ x 10)) 20) -> 30 

问题是什么时候引用函数或符号来保存功能定义。除非你“不正确地”做事,否则几乎没有答案。通过“不正确”,我的意思是说,当你应该做相反的事时,你将一个功能定义放置在符号的值单元格或功能单元格中。请参见下面的示例来更好地理解:

例1 - 中值单元存储功能
假设你需要使用“应用”与预计可变数量的参数的函数。一个这样的例子是符号+。 Lisp将+视为常规符号。功能定义存储在+的功能单元中。你可以,如果你喜欢使用

(setq + "I am the plus function"). 

如果评估

+ -> "I am the plus function" 

但是值分配给它的值单元格,(1 + 2)仍正常工作。

(+ 1 2) -> 3 

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

(+ '(1 2 3)) -> Wrong type... 

原因是+期望它的参数是函数。申请解决此问题

(apply #'+ '(1 2 3)) -> (+ 1 2 3) -> 6 

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

有没有办法使用+不带引号的应用。就在这里。你可以只评估了下面的代码:

(setq + (symbol-function '+)) 
(apply + '(1 2 3)) 

这将计算为6,预期因为Lisp的评估板(应用+'(1 2 3)),现在发现+的存储+功能定义的价值细胞。

例2 - 在值单元格中存储功能定义
假设你存储在符号的价值细胞的功能定义。这是实现如下:

(setq AFunc (lambda (x) (* 10 x))) 

评估

(AFunc 2) 

因为Lisp语言不能找到AFunc的功能细胞的功能产生错误。通过使用funcall来解决这个问题,它告诉Lisp使用符号的值单元格中的值作为功能定义。你用“funcall”来做到这一点。

(funcall AFunc 2) 

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

(funcall AFunc 2) -> 20 

你可以通过使用FSET放置拉姆达形式在符号的功能细胞避免使用funcall具有:

(setf AFunc (lambda (x) (* 10 x))) 
(AFunc 2) 

此代码块将返回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)) 

此代码块将返回

(1 4 9) 

原因广场未在上面的例子中引述的是,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))) 
)  

上面的代码将评估为

"Symbol's value -> Symbol's Value" 
"Symbol's function -> (lambda nil Symbol's Function)" 
nil 

报价需要在两个

(fset 'ASymbol (lambda() "Symbol's Function")) 

(symbol-value 'ASymbol) 

(symbol-function 'ASymbol) 

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

我希望这篇冗长的文章对某人有用。