时间:2010-07-24 14:41:14

标签: common-lisp

2 个答案:

答案 0 :(得分:37)

答案 1 :(得分:8)

最好避免#'在大多数情况下,因为它主要是"不必要的,使你的代码更加冗长。当需要某种形式的引用时有一些例外(见下面的例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-Lisps。
  4. 现在,您需要大致了解Lisp如何评估S表达式(括号表达式)。每个S表达式的评估大致如下:

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

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

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

    4. 将从CAR获得的函数应用于从CDR中的每个元素获得的值。
    5. 上述过程意味着 UNQUOTED S-expression的CAR中的任何符号必须在其功能单元中具有有效的功能定义。

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

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

      生成错误?这是因为#'(lambda(x)(+ x 10)),即S表达式的CAR,由于功能引用#&#39而未被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没有得到+功能单元中的功能定义。实际上,如果我们没有将其值单元格设置为"我是加号函数,"根据apply的要求,Lisp会检索nil,这不是函数。

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

      (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将lambda表单放在符号的函数单元格中来避免使用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)  
      

      在上面的例子中没有引用Square的原因是S-expression是根据我在上面概述的规则评估的。首先,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,symbol-value和symbol-function正常工作。

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