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.
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.