【问题标题】:Why does this function return a different value every time?为什么这个函数每次都返回不同的值?
【发布时间】:2012-02-16 06:54:04
【问题描述】:

有人可以解释以下行为吗?具体来说,为什么函数每次都返回不同的列表?为什么每次调用函数时都没有将some-list初始化为'(0 0 0)

(defun foo ()
  (let ((some-list '(0 0 0)))
    (incf (car some-list))
    some-list))

输出:

> (foo)
(1 0 0)
> (foo)
(2 0 0)
> (foo)
(3 0 0)
> (foo)
(4 0 0)

谢谢!

编辑:

另外,假设我希望函数每次都输出'(1 0 0),那么推荐的实现这个函数的方法是什么?

【问题讨论】:

    标签: scope lisp common-lisp literals


    【解决方案1】:

    '(0 0 0) 是一个文字对象,它被假定为一个常量(尽管不受修改保护)。因此,您每次都在有效地修改同一个对象。要在每个函数调用中创建不同的对象,请使用 (list 0 0 0)

    因此,除非您知道自己在做什么,否则您应该始终仅将文字列表(如 '(0 0 0))用作常量。

    【讨论】:

    • 最好补充一点,准引用也不能保证返回新列表。
    • "除非你知道,你在做什么" 修改文字数据的行为是未定义的。根据规范,您实际上无法知道自己在做什么(可以肯定地),因此“您应该始终仅将文字列表(如'(0 0 0))用作常量”。
    【解决方案2】:

    附带说明,在 sbcl REPL 中定义此函数会收到以下警告:

      caught WARNING:
        Destructive function SB-KERNEL:%RPLACA called on constant data. 
        See also: 
          The ANSI Standard, Special Operator QUOTE 
          The ANSI Standard, Section 3.2.2.3
    

    这很好地暗示了手头的问题。

    【讨论】:

      【解决方案3】:

      '(0 0 0) 在代码中是文字数据。修改此数据具有未定义的行为。 Common Lisp 实现可能不会在运行时检测到它(除非数据例如放置在某些只读内存空间中)。但它可能会产生不良影响。

      • 您会看到,这些数据可能(并且经常)在同一函数的各种调用之间共享

      • 一个更微妙的可能错误是:Common Lisp 已经定义了各种优化,这些优化可以由编译器完成。例如允许编译器重用数据:

      例子:

      (let ((a '(1 2 3))
            (b '(1 2 3)))
        (list a b))
      

      在上面的代码 sn-p 中,编译器可能会检测到ab 的文字数据是EQUAL。然后它可能使两个变量都指向相同的文字数据。修改它可能会起作用,但是从 ab 可以看到更改。

      总结: 文字数据的修改是几个细微错误的来源。如果可能的话,避免它。然后你需要cons 新的数据对象。 Consing 通常意味着在运行时分配新的、新的数据结构。

      【讨论】:

        【解决方案4】:

        想自己写一篇,但在网上找到了一篇不错的:

        CommonLisp 具有一流的函数,即函数是对象 可以在运行时创建,并作为参数传递给其他函数。 --AlainPicard 这些一流的函数也有自己的状态,所以它们是函子。所有 Lisp 函数都是函子;没有 “只是代码”和“函数”的函数之间的分离 对象”。状态采用捕获的词法变量的形式 绑定。您不需要使用 LAMBDA 来捕获绑定;一种 顶级的 DEFUN 也可以做到:(let ((private-variable 42)) (defun 富 () ...))

        代替 ... 的代码在其词法中看到私有变量 范围。此变量的一个实例与该变量相关联 并且只有全局绑定到符号 FOO 的函数对象;这 在计算 DEFUN 表达式时捕获变量。 然后,此变量的作用类似于 C 中的静态变量。或者, 或者,您可以将 FOO 视为具有 “实例变量”。 --KazKylheku

        参考 http://c2.com/cgi/wiki?CommonLisp

        【讨论】:

        • 您能解释一下您引用的文字与问题的关系吗?我可能遗漏了一些东西,但我没有看到它。
        • 该文本解释了函数如何成为 Lisp 中的一等对象,并且确实具有“状态”。声明的变量是函数“状态”的一部分。正如文中解释的那样,这与在C中声明静态局部变量非常相似。文中哪一部分与这个问题无关?
        • 那部分根本不是正在发生的事情。您的报价谈到“捕获的词法变量绑定”。但是some-listfoo 的局部变量,它不是捕获的变量,因此不是foo 状态的一部分。在每次调用foo 时,some-list 将有一个唯一的绑定(正如 Vsevolod 解释的那样,它将指向相同的“常量”列表,这解释了 OP 的行为)。这与修改捕获变量的函数完全不同。
        猜你喜欢
        • 2017-01-09
        • 1970-01-01
        • 2012-04-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-06
        相关资源
        最近更新 更多