【问题标题】:How to update a node in lisp如何在 lisp 中更新节点
【发布时间】:2015-05-23 03:10:13
【问题描述】:

我正在用 lisp 操作一棵树。 我认为函数参数是可变的..

例如,我创建了一个列表 fs 和一个向其中添加元素而不返回列表本身的函数(我真的不想返回它)。

(defparameter fs '(1 2 3 4))

(defun addtolist (fs)
  (append fs '(6))
)

(print (addtolist fs))
;;(1 2 3 4 6)
(print fs)
;;(1 2 3 4)

我不明白为什么列表没有保留修改后的内容。 有什么想法吗?

【问题讨论】:

  • 您说:“我认为函数参数是可变的……”您为什么这么认为?然后你说'...创建一个列表 fs':你没有创建一个列表fs。您创建了一个变量fs,它的值是一个列表。

标签: lisp elisp common-lisp


【解决方案1】:

首先,关于 ANSI Common Lisp 的使用说明:

(defparameter fs '(1 2 3 4))

这里,defparameter 引入了 fs 作为动态变量。这意味着其中一件事是符号fs 本身被标记为“特殊”,这会影响其所有后续绑定。

(defun addtolist (fs)
  (append fs '(6))
)

结果是,这里的函数参数fs 不是通常预期的词法变量,而是动态变量。这几乎可以肯定是无意的,并且可以改变行为。

这个问题可以通过命名规则来避免,最常见的是“耳罩约定”:前导星号和尾随星号:

(defparameter *my-dynamic-variable* 42)

注意 Common Lisp 中的标准动态变量是如何以这种方式命名的:*standard-output**print-base**random-state**package* 等等。

我不明白为什么列表没有保留修改后的内容。

这是因为append 函数本身会计算一个新列表,而不管旧列表。 (新列表可以使用旧列表的片段,但旧列表不受干扰。)

在这种特殊情况下,可以使用append 的破坏性版本,即nconc 函数。 nconc 将重写原始列表的尾部,使其指向(6)。但是,有两个问题:

  1. 输入列表是文字。根据 ANSI Common Lisp 规范,修改文字是未定义的行为。无法将文字 '(1 2 3 4) 修改为 (1 2 3 4 6)。文字对象必须被视为只读。

  2. 这种方法并不通用。即使我们在addtolist 中使用nconc,并确保我们不给它提供文字(以避免未定义的行为),我们的addtolist 也会被破坏。例如,它不能添加到空列表中!

在 Lisp 中,我们没有可以附加而不返回的“类似袋子”的列表。空列表由一个符号表示,符号nil,它不是一个没有任何内容的容器。我们不能将nil 更改为非空列表。

通过将列表存储在通用位置(一些setf-able 位置,例如变量),在 Lisp 中模拟了“类似袋子”的列表。更新列表时,我们将新版本分配回变量中。

当对象必须具有关联对象的列表时,即被封装。例如,对于结构:

(defstruct notifier
  (listeners))      ;; a notifier has listeners

(defun add-listener (notifier listener)
  (pushnew listener (notifier-listeners notifier)))

(defun remove-listener (notifier listener)
  (setf (notifier-listeners notifier) ;; candidate for define-modify-macro
        (remove listener (notifier-listeners notifier))))

[1]> (defvar *n* (make-notifier))
*N*
[2]> *n*
#S(NOTIFIER :LISTENERS NIL)
[3]> (add-listener *n* 4)
(4)
[4]> (add-listener *n* 5)
(5 4)
[5]> *n*
#S(NOTIFIER :LISTENERS (5 4))
[6]> (add-listener *n* 6)
(6 5 4)
[7]> *n*
#S(NOTIFIER :LISTENERS (6 5 4))
[8]> (remove-listener *n* 5)
(6 4)
[9]> *n*
#S(NOTIFIER :LISTENERS (6 4))
[10]> (remove-listener *n* 6)
(4)
[11]> (remove-listener *n* 4)
NIL
[12]> *n*
#S(NOTIFIER :LISTENERS NIL)

“袋状”列表是一个巨大的劣势,因为如果不参与命令式编程,就无法使用它们。此类列表上的所有操作都是破坏性操作。对于 Lisp 中列表的各种用法,例如生成和转换深度嵌套的句法结构,这非常麻烦、低效且容易出错。

正如您在上面的示例中所见,如果我们将功能列表“停放”到某个众所周知的位置(例如结构的插槽),并处理该插槽,我们可以轻松获得“类似袋子”的列表作为“包”。破坏性的包操作,如追加和删除,会转化为分配到那个位置。

【讨论】:

    【解决方案2】:

    并非所有函数都会修改其参数;事实上,大多数(比如append)没有。

    我想你要找的是nconc

    【讨论】:

    • 那如何在列表中添加元素并保留修改而不返回列表?
    • 谢谢。如果列表 fs 为空,则不保存该值。我该如何解决这个问题?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-02
    相关资源
    最近更新 更多