1

I am wondering about how one could do something as follows in Common Lisp. Suppose I have an object (entity) in memory that is unique at a certain time. What I would like to do is set some variable to be the state of that object, as snapshot at a certain time. The original entity may then evolve. However, I would like to make sure that the variable still points to the state of that entity in the past.

It appears that what I need is something along of the lines of a deep-copy + a setter. The complicating factor is that, at times, the nature of the entity is unknown. It may be an array or it may be a hashtable. It may likewise be an object.

Any advice is appreciated.

MadPhysicist
  • 5,401
  • 11
  • 42
  • 107
  • 1
    There's no general-purpose copy function in Common Lisp. You need to write a shallow or deep copy function that's specific to the types of entities you're using. – Barmar Nov 05 '18 at 20:10
  • 1
    That's why we have functions like `COPY-LIST` (shallow copy of a list), `COPY-TREE` (deep copy of conses), and `COPY-SEQ` (shallow copy of a list or vector). – Barmar Nov 05 '18 at 20:11
  • 1
    There's no built-in function for making a copy of a hash table, you have to write that yourself. https://stackoverflow.com/questions/26045442/copy-hash-table-in-lisp – Barmar Nov 05 '18 at 20:13
  • 3
    This sounds like a case where you would usually use immutable data structures (e.g. [FSet](https://github.com/slburson/fset) or [Sycamore](https://github.com/ndantam/sycamore)). – jkiiski Nov 05 '18 at 21:06
  • For deep copies, you can use existing serialization libraries, like https://github.com/conspack/cl-conspack – coredump Nov 07 '18 at 09:48

1 Answers1

3

The only thing you need is immutable objects and only update the bindings. setq (and setf with a symbol as first argument) does this perfectly. Here are some examples:

(defparameter *test* '(1 2))
(defparameter *test2* *test*) ; a copy
(setf *test* (cdr *test*))    ; *test* is (2), but *test2* is still (1 2)
(defparameter *test3* *test*) ; a new copy
(setf *test* (cons 3 *test*)) ; *test* is (3 2), but *test2* is still (1 2) and *test3* is still (2)

push and pop does this for you. Here is the same code:

(defparameter *test* '(1 2))
(defparameter *test2* *test*) ; a copy
(pop *test*)                  ; *test* is (2), but *test2* is still (1 2)
(defparameter *test3* *test*) ; a new copy
(push 3 *test*)               ; (3 2), but *test2* is still (1 2) and *test3* is still (2)

What I do not do here is (setf (car *test*) 3) since that mutates the object and all references will get the same object since they point at the object I changed.

So if you have some sort of state that is more complex, like a hash table, you would need to turn it into a tree so that you can change log(n) nodes per update and it will work in the same manner, that old references still have the same state.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • Could you expound on the last paragraph? – MadPhysicist Nov 08 '18 at 21:26
  • By the way, does this still work if the datastructure itself has references to other datastructures that may change from under it? In this case I mean values in the hashtable that may change from one snapsnot to another. – MadPhysicist Nov 08 '18 at 21:35
  • @MadPhysicist Most functional data structures are trees. You update the three and get a new tree back. The old reference is the complete old tree and the new is the altered tree. The nodes themselves cannot be mutated, but you can replace the node with a new one. That will create a new tree with log(n) nodes and a new value. The alternative would be to deep copy every time, but then you use more storage since the values will have no sharing. – Sylwester Nov 08 '18 at 21:51