Lisp函数如何在此代码中记住状态?

时间:2013-07-25 18:38:50

标签: static lisp state

我在网站http://www.ccs.neu.edu/home/shivers/newstyle.html看到了一段代码:

> (defun element-generator ()
    (let ((state '(() . (list of elements to be generated)))) ;() sentinel.
      (let ((ans (cadr state)))       ;pick off the first element
        (rplacd state (cddr state))   ;smash the cons
        ans)))
ELEMENT-GENERATOR
> (element-generator)
LIST
> (element-generator)
OF
> (element-generator)
ELEMENTS
> (element-generator)
TO
> (element-generator)
BE
> (element-generator)
GENERATED

我不明白该功能如何记住状态。每次函数运行时,是否state重新定义到整个列表?为什么这两层let(这是必要的)?如果有人能够解释这个功能如何运作,我们将不胜感激。

4 个答案:

答案 0 :(得分:7)

state(let ((state '(() . (list of elements to be generated)))) ...)的值是引用的文字,并且正在修改(正如this answer中所解释的那样是未定义的行为)。此行为已经讨论过其他问题,例如:

答案 1 :(得分:0)

(defun element-generator ()

上面定义了一个函数。

  (let ((state '(() . (list of elements to be generated)))) ;() sentinel.

这里定义了一个局部变量。代码中有文字数据。想想Lisp函数本身的数据,部分代码就是这个带有列表的构造。在Common Lisp标准中,未定义当尝试修改此数据对象时会发生什么。

由于它是文字数据对象,因此只有一个。该变量不会绑定到刚刚建立的数据。因此,在每次调用时,变量都指向相同的文字数据。

    (let ((ans (cadr state)))       ;pick off the first element

Above创建一个临时变量并保存第一个元素。

      (rplacd state (cddr state))   ;smash the cons

在上面的代码中,第一个元素从列表中删除。如前所述,这不是推荐的做法,因为列表是文字数据。

      ans)))

Above返回保存的值。

这个LET成语是由宏PROG1提供的Common Lisp。

答案 2 :(得分:0)

这超出了CL标准的范围。一些CL实现可能会做这些奇怪的事情,但其他可能不会。我记得当我学习CL时,我发现了类似的东西。我的版本是这样的:

(defun counter1 (&optional (x '(-1)))
   (rplaca x (+ (car x) 1))
   (car x))

(counter1) ; ==> 0
(counter1) ; ==> 1
(counter1) ; ==> 2

此处和您的代码中发生的是可选的xstate是常量。它只存在其中一个,如果你改变它,你永久地改变它。在我的例子中;如果默认值为(cons -1 ()),如果我没有提供x,它将始终返回0。为什么你有两个嵌套的let是因为(cadr state)rplacd之前和之后不同。我建议您将其删除,并将ans替换为(cadr state)以查看差异。

如果您想在标准中使用相同的功能,以下是示例代码的外观:

(defun element-generator ()
  (let ((state (list () 'list 'of 'elements 'to 'be 'generated))); Make closure
    #'(lambda ()                        ;create function
        (let ((ans (cadr state)))       ;pick off the second element
          (rplacd state (cddr state))   ;mutate the list in the closure
          ans))))                       ;return element

(setf (symbol-function 'gen1) (element-generator))
(setf (symbol-function 'gen2) (element-generator))
(gen1) ; ==> LIST
(gen1) ; ==> OF
(gen1) ; ==> ELEMENTS
(gen2) ; ==> LIST
(gen2) ; ==> OF
(gen1) ; ==> TO

这里为元素生成器的每个实例化获取一个新的闭包,当你调用它返回的函数时,你将从该闭包中获取元素。查看返回的两个函数如何具有自己的列表版本。既然我们没有破解标准,我们也可以将其重写为mutate状态而不是列表:

(defun element-generator ()
  (let ((state (list 'list 'of 'elements 'to 'be 'generated))) ; create closure
    #'(lambda ()                       ;create function
        (let ((ans (car state)))       ;pick off the first element
          (setf state (cdr state))     ;mutate state to point to next element
          ans))))                      ;return first element

它的行为相同。

答案 3 :(得分:0)

'(() . (list of elements to be generated))

这是一个静态列表,但是rplacd无论如何都会改变它...修改函数定义,以便下次运行时列表更短。如果您查看调用之间的函数定义,您会看到它变短。一些lisp实现在显示函数定义方面比其他实现更好。

您只应在刚创建列表时使用破坏性列表操作(或者您真正了解自己在做什么)。