I'm looking for a simple way to return a new structure which is a copy of an existing one with some fields changed, without modifying the original.
I get that you can use setf
to change the data in one of the fields, like so:
[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
But as I stated, this essentially destroys the original data, which isn't what I'm going for.
I could of course do something like this:
(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
But it seems more like a work-around than anything.
In Erlang you can do something like this:
-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
No data modified.
PS Yes I'm aware that Common Lisp isn't Erlang; but Erlang made it convenient to work with records/structures immutably, and since functional style is encouraged in Common Lisp, it would be nice if there was a similar option available.
Erlang records are similar to Prolog structures. The Prolog I know implements this as a predicate named update_struct/4
which admits a macro expansion: it takes a type parameter and expands into two unifications. The same kind of processing is done in Erlang, according to the documentation. In Common Lisp, we don't need to pass the type explicitly, as shown with the following update-struct
function:
(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)))
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)
Rainer Joswig kindly pointed out that:
There is one thing which is undefined by the standard: using SLOT-VALUE on structure objects is undefined. But it should work on most implementations, as they provide this feature. The only implementation where it does not seem to work is GCL.
Indeed, the HyperSpec says about SLOT-VALUE
:
Note in particular that the behavior for conditions and structures is not specified.
An implementation might behave differently if the structured is backed by a list or vector (see the ̀:TYPE
option). Anyway, if you need to be portable you'd better use classes.
This topic was also explained in detail by Rainer in common lisp: slot-value for defstruct structures.
Consider also using property lists, which play nicely with an immutable approach.
Say your initial list x
is:
(:a 10 :b 30)
Then (list* :b 0 x)
is the following list:
(:b 0 :a 10 :b 30)
... where the most recent value associated with :b
shadows the ones after (see GETF
).
The bindings
list is a property list, with alternation of keys and values (like keyword parameters).
In the LOOP
expression above, I am iterating over the list of bindings using ON
, which means that I am considering each sublist instead of each element. In other words (loop for x on '(a b c d))
successively binds x
to (a b c d)
, (b c d)
, (c d)
and finally (c)
.
However, since I provide a custom BY
argument, the next element is computed using CDDR
, instead of the default CDR
(so, we advance by two cells instead of by one).
That allows me to consider each pair of key/value elements in the list, and bind them to slot
and value
thanks to the destructuring syntax.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With