【问题标题】:Why #' is used before lambda in Common Lisp?为什么在 Common Lisp 中在 lambda 之前使用 #'?
【发布时间】:2011-03-20 12:34:13
【问题描述】:

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

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

而不仅仅是

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

这似乎也有效。我开始学习 Common Lisp,并且有一些 Scheme 的背景,这让我很感兴趣。

编辑:我知道您需要带有函数名称的#',因为它们与变量位于不同的命名空间中。我的问题只是关于 lambda 之前的 #',因为 lambda 已经返回了一个函数对象(我认为)。 #'-less lambdas 由于宏扩展而起作用的事实使它更有趣......

【问题讨论】:

    标签: common-lisp


    【解决方案1】:

    #'foo 是读者对(function foo)abbreviation

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

    您可能想search for "Lisp-1 vs. Lisp-2"、查看其他Stackoverflow questions 或阅读old article by Pitman and Gabriel 以了解有关多个命名空间(也称为slotscells)概念的更多信息 个符号)。

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

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

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

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

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

    来自functiondocumentation

    如果 name 是一个 lambda 表达式,那么 返回词法闭包。

    我认为差异也与调用 lambda 表单有关:((lambda ...) ...) 将其视为要评估的表单,而不是 (funcall #'(lambda ...) ...)。如果您想了解有关该主题的更多信息,请联系c.l.l thread

    来自该线程的一些引用:

    (lambda (x) ... 本身只是一些 不带引号的列表结构。这是它的 外观作为论据 FUNCTION 特殊形式 (function (lambda (x) ... 导致 函数对象存在

    和:

    还有一个事实是 LAMBDA 宏是一个相当晚的 除了 ANSI Common Lisp,所有 真正的老家伙(即,像我一样) 当你需要的时候学会了他们的口齿不清 将 #' 提供给 lambda 表达式 在映射函数中。否则 不存在的 lambda 函数将 被调用。

    宏的添加改变了这一点,但是 我们中的一些人太固执己见 想要改变。

    【讨论】:

    • 我知道命名空间的区别。但我预计,由于 lambda 直接返回一个函数对象(或者是吗?),所以不需要调用 'function' 或 #' 。为什么会这样?
    • 嗯,所以不,纯 lambda 不返回函数对象...感谢您的解释。
    • 另一个讨论 (funcall (lambda ...)) 和 ((lambda ...)) 双重语法的链接:letoverlambda.com/textmode.cl/guest/chap4.html#sec_4
    • 这是很好的了解,尤其是如果您使用的是 CL 的亲戚或前身。
    【解决方案2】:

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

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

    简单说明
    首先,让我们研究一个最好避免引用的案例。函数是对自己求值的第一类对象(例如,像任何其他对象一样对待,包括将它们传递给函数并将它们分配给变量的能力)。匿名函数(例如 lambda 形式)就是这样一个例子。在 Emacs Lisp (M-x ielm RET) 或任何 ANSI common 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 没有值和功能单元的概念。这样的 Lisps 统称为 1-Lisps。

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

    1. 如果引用,则返回未评估的内容
    2. 如果未加引号,则获取其 CAR(例如第一个元素)并使用以下规则对其进行评估:

    一个。如果是原子,只需返回它的值(例如 3 -> 3,“pablo” -> “pablo”)
    湾。如果是 S 表达式,则使用相同的整体过程对其进行评估
    C。如果是符号,则返回其函数单元格的内容

    1. 评估 S 表达式的 CDR 中的每个元素(例如,除了列表的第一个元素之外的所有元素)。
    2. 将从 CAR 获得的函数应用于从 CDR 中的每个元素获得的值。

    上述过程意味着UNQUOTED S 表达式的 CAR 中的任何符号都必须在其函数单元格中具有有效的函数定义。

    现在,让我们回到文章开头的示例。为什么

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

    产生错误?之所以如此,是因为 #'(lambda (x) (+ x 10)),即 S 表达式的 CAR,由于函数引号 #' 而不会被 Lisp 解释器求值。

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

    不是函数,而是

    (lambda (x) (+ x 10))
    

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

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

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

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

    因此,问题是何时引用包含函数定义的函数或符号。除非您“错误地”做事,否则答案几乎永远不会。 “不正确”是指当你应该做相反的事情时,你将函数定义放在符号的值单元格或函数单元格中。请参阅以下示例以更好地理解:

    示例 1 - 存储在值单元格中的函数
    假设您需要将apply 与需要可变数量参数的函数一起使用。一个这样的例子是符号+。 Lisp 将+ 视为常规符号。功能定义存储在+ 的功能单元中。如果您喜欢使用,可以为其值单元格分配一个值

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

    如果你评估

    + -> "I am the plus function"
    

    但是,(+ 1 2) 仍然可以正常工作。

    (+ 1 2) -> 3
    

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

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

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

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

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

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

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

    这将评估为6,正如预期的那样,因为当 Lisp 评估 (apply + '(1 2 3)) 时,它现在发现 + 的函数定义存储在 + 的值单元格中。

    示例 2 - 在值单元格中存储功能定义
    假设您将函数定义存储在符号的值单元格中。实现方式如下:

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

    评估

    (AFunc 2)
    

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

    (funcall AFunc 2)
    

    假设存储在符号值单元格中的函数定义是有效的,

    (funcall AFunc 2) -> 20  
    

    您可以避免使用funcall,方法是使用fset 将lambda 形式放在符号的函数单元格中:

    (fset 'AFunc (lambda (x) (* 10 x)))  
    (AFunc 2)  
    

    此代码块将返回 20,因为 lisp 在 AFunc 的函数单元格中找到了函数定义。

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

    (defun SquareNumberList (AListOfIntegers)
        "A silly function with an unnecessary
       local function."
      (let ((Square (lambda (ANumber) (* ANumber ANumber))))
        (mapcar Square AListOfIntegers)
        )
      )  
    
    (SquareNumberList '(1 2 3))  
    

    此代码块将返回

    (1 4 9)  
    

    上面的例子中没有引用 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)))
      )    
    

    上面的代码将评估为

    "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、符号值和符号函数正常工作。

    我希望这篇冗长的帖子对某人有用。

    【讨论】:

    • (setf AFunc (lambda (x) (* 10 x))) 后跟 (AFunc 2) 产生错误:The function COMMON-LISP-USER::AFUNC is undefined.
    • 你是对的。我在(setf AFunc (lambda (x) (* 10 x))) 中有错字,然后是(AFunc 2)。我应该在 Emacs Lisp 中输入 (fset 'AFunc (lambda (x) (* 10 x))),它返回 (closure (t) (x) (* 10 x))。评估(AFunc 2) 返回20fset 设置符号的功能单元。我已相应地更正了文字。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-18
    • 2012-08-22
    • 1970-01-01
    • 1970-01-01
    • 2015-07-03
    • 2013-03-24
    • 2021-09-25
    相关资源
    最近更新 更多