返回更改了字段的新结构

Ele*_*fee 6 records common-lisp immutability shallow-copy

我正在寻找一种简单的方法来返回一个新结构,这是一个现有的结构的副本,其中一些字段已更改,而不修改原始结构.

我知道你可以setf用来改变其中一个字段中的数据,如下所示:

[1]> (defstruct foo bar)
FOO
[1]> (defvar quux (make-foo :bar 12))
QUUX
[1]> (foo-bar quux)
12
[1]> (setf (foo-bar quux) 15)
15
[1]> (foo-bar quux)
15
Run Code Online (Sandbox Code Playgroud)

但正如我所说,这基本上破坏了原始数据,这不是我想要的.

我当然可以这样做:

(defstruct foo bar baz) ; define structure
(defvar quux (make-foo :bar 12 :baz 200)) ; make new instance
(defvar ping (copy-foo quux)) ; copy instance
(setf (foo-bar ping) 15) ; change the bar field in ping
Run Code Online (Sandbox Code Playgroud)

但它看起来更像是一种解决方法而不是任何东西.

在Erlang中,您可以执行以下操作:

-record(foo, {bar, baz}). % defines the record

example() ->
  Quux = #foo{bar = 12, baz = 200}, % creates an instance of the record
  Ping = Quux#foo{bar = 15}. % creates a copy with only bar changed
Run Code Online (Sandbox Code Playgroud)

没有数据修改.

PS是的我知道Common Lisp不是Erlang; 但是Erlang使得使用记录/结构变得更加方便,并且由于在Common Lisp中鼓励使用功能样式,如果有类似的选项可用,那将会很好.

cor*_*ump 5

Erlang 记录类似于Prolog结构。我知道的Prolog将update_struct/4其作为谓词实现,它允许宏扩展:它接受类型参数,并扩展为两个统一体。根据文档,在Erlang中完成了相同类型的处理。在Common Lisp中,我们不需要显式传递类型,如以下update-struct函数所示:

(defun update-struct (struct &rest bindings)
  (loop
    with copy = (copy-structure struct)
    for (slot value) on bindings by #'cddr
    do (setf (slot-value copy slot) value)
    finally (return copy)))
Run Code Online (Sandbox Code Playgroud)

CL-USER> (defstruct foo bar baz)
FOO
CL-USER> (defparameter *first* (make-foo :bar 3))
*FIRST*
CL-USER> (defparameter *second* (update-struct *first* 'baz 2))
*SECOND*
CL-USER> (values *first* *second*)
#S(FOO :BAR 3 :BAZ NIL)
#S(FOO :BAR 3 :BAZ 2)
Run Code Online (Sandbox Code Playgroud)

规格

Rainer Joswig友善指出:

标准未定义一件事:在结构对象上使用SLOT-VALUE是未定义的。但是它应该可以在大多数实现中使用,因为它们提供了此功能。似乎不起作用的唯一实现是GCL。

实际上,HyperSpec谈到SLOT-VALUE

请特别注意,未指定条件和结构的行为。

如果结构化对象由列表或向量支持(请参见?:TYPE选项),则实现的行为可能会有所不同。无论如何,如果您需要具有可移植性,则最好使用类。Rainer在常见的lisp中也详细解释了该主题:defstruct结构的slot-value

其他不可变的数据结构

还可以考虑使用属性列表,它可以很好地与不可变方法配合使用。

说您的初始列表x是:

(:a 10 :b 30)
Run Code Online (Sandbox Code Playgroud)

然后(list* :b 0 x)是以下列表:

(:b 0 :a 10 :b 30) 
Run Code Online (Sandbox Code Playgroud)

...与之关联的最新值:b在其后面阴影(请参阅参考资料GETF)。

循环细节

bindings列表是一个属性列表,具有键和值(如关键字参数)的交替。在LOOP上面的表达式中,我使用遍历绑定列表ON,这意味着我正在考虑每个子列表而不是每个元素。换句话说(loop for x on '(a b c d))先后结合x(a b c d)(b c d)(c d)和最后(c)

但是,由于我提供了一个自定义BY参数,因此下一个元素是使用CDDR而不是默认值来计算的CDR(因此,我们前进两个像元而不是一个像元)。这样一来,我就可以考虑列表中的每对键/值元素,并将其绑定到slotvalue感谢解构语法。