Search code examples
clojure

How to update fields of a Clojure record


I have this record:

(defrecord Point [x y z])
(def location (Point. 1 2 3))

Now I want to create a new variable new-location based on location, but with the z coordinate changed. I know that I could manually copy all the fields i.e. (def new-location (Point. (:x location) (:y location) 99)), but is there a simpler way to functionally update only a few fields of a Clojure record?

Note that I am looking for an immutable solution. Clojure: Update value of record field is about mutation, and therefore does not answer my question.


Solution

  • All you need is assoc:

    (ns tst.demo.core
      (:use demo.core tupelo.core tupelo.test))
    
    (defrecord Point [x y z])
    (dotest
      (newline)
      (def p1 (->Point 1 2 3))
      (spyx p1)
      (spyx (type p1))
    
      (newline)
      (def p2 (assoc p1 :z 99))
      (spyx p2)
      (spyx (type p2))
    
      (newline)
      (def m2 (into {} p1 ))
      (spyx m2)
      (spyx (type m2))
    
      (newline)
      (def m3 (dissoc p1 :z ))
      (spyx m3)
      (spyx (type m3))
    
      (newline)
      (def m4 (assoc p1 :extra 42 ))
      (spyx m4)
      (spyx (type m4))
    
    )
    
    
    

    with result

    -----------------------------------
       Clojure 1.10.3    Java 15.0.2
    -----------------------------------
    
    Testing tst.demo.core
    
    p1 => #tst.demo.core.Point{:x 1, :y 2, :z 3}
    (type p1) => tst.demo.core.Point
    
    p2 => #tst.demo.core.Point{:x 1, :y 2, :z 99}
    (type p2) => tst.demo.core.Point
    
    m2 => {:x 1, :y 2, :z 3}
    (type m2) => clojure.lang.PersistentArrayMap
    
    m3 => {:x 1, :y 2}
    (type m3) => clojure.lang.PersistentArrayMap
    
    m4 => #tst.demo.core.Point{:x 1, :y 2, :z 3, :extra 42}
    (type m4) => tst.demo.core.Point
    
    
    

    Note that a simple assoc preserves the result as a record of the same type. Some operations create a map result, as we see with dissoc creating m3.

    However, using assoc to add an :extra field to create m4 preserves the type as a Point record. Surprise, surprise!

    Build from my favorite template project.