I am trying to represent gui elements in Clojure/Script. The problem is that the elements are nested and need to communicate with each other. I thought to pass references to each other, but it seems I can't just pass mutual references to each other, as I naively thought.
Code I tried
(defrecord Element [position size parent children state type value])
(def parent (atom (Element. ...)))
(def child (atom (Element. ...)))
(assoc @parent :child child)
(assoc @child :parent parent)
this doesn't work.
What is the idiomatic solution in Clojure for this kind of problem?
Update: mutual referencing doesn't seem to work:
(defrecord Element [position size parent children state type value])
(def parent (atom (Element. {:x 0 :y 0} {:width 1600 :height 900} nil nil :collapsed :container nil)))
(def child (atom (Element. {:x 5 :y 5} {:width 100 :height 100} nil nil :collapsed :container nil)))
(swap! parent assoc :child child)
(swap! child assoc :parent parent)
#object[InternalError InternalError: too much recursion]
cljs.core.PersistentArrayMap.prototype.cljs$core$ILookup$_lookup$arity$2 (jar
:file:/C:/Users/user/.m2/repository/org/clojure/clojurescript/1.10.520/clojuresc
ript-1.10.520.jar!/cljs/core.cljs:6811:10)
Maybe in Clojure you don't do mutual references?
You're assoc
iating the parent to the child, but then throwing away the new Element
. You need to set the value in the atom using reset!
or swap!
:
(swap! parent assoc :child child) ; Equivalent to (swap! parent #(assoc % :child child))
(swap! child assoc :parent parent)
swap!
is similar to update
, but it replaces the value held within an atom
.
Also note, you gave a field the name children
, but in your example, you're referring to it using :child
. children
suggests multiple children though, so I'd change it to start as a vector instead of nil
, then conj
to it. I'd also create a new-element
helper:
(defrecord Element [position size parent children state type value])
(defn new-element [position size]
(->Element position size nil [] :collapsed :container nil))
(def parent (atom (new-element {:x 0 :y 0} {:width 1600 :height 900})))
(def child (atom (new-element {:x 5 :y 5} {:width 100 :height 100})))
(swap! parent update :children conj child)
(swap! child assoc :parent parent)
To address the error that you're getting, it runs fine for me in Clojure.
If I print parent
and child
after the above code runs, I get an infinite output because to print the parent you need to print the child, and to print the child you need to print the parent... It still works for me though.
Make sure that error isn't coming about because you're trying to print the structure out in a REPL.
This could also be a limitation of Clojurescript. I can't answer for that unfortunately since I'm not familiar with Cljs.
I'll delete my answer if this ends up not being helpful.
I'll also note that nesting atoms isn't a great idea. atom
s are really mutable containers to hold immutable objects. It may be cleaner to have an ID system, and have each parent/child hold a numeric ID to it's child/parent. Then you have one single atom for your whole program. The atoms can hold a map of ID->element for easy lookups.