Is it possible to have a structure nested within a structure in Clojure? Consider the following code:
(defstruct rect :height :width)
(defstruct color-rect :color (struct rect))
(defn
#^{:doc "Echoes the details of the rect passed to it"}
echo-rect
[r]
(println (:color r))
(println (:height r))
(println (:width r)))
(def first-rect (struct rect 1 2))
;(def c-rect1 (struct color-rect 249 first-rect)) ;form 1
;output "249 nil nil"
(def c-rect1 (struct color-rect 249 1 2)) ;form 2
;output "Too many arguments to struct constructor
(echo-rect c-rect1)
Of course this is a contrived example but there are cases where I want to break a large data structure into smaller substructures to make code easier to maintain. As the comments indicate if I do form 1 I get "249 nil nil" but if I do form 2 I get "Too many arguments to struct constructor".
If I'm approaching this issue in the wrong way, please tell me what I should be doing. Searching the Clojure google group didn't turn up anything for me.
Edit:
I guess I wasn't as clear in the statement of my question as I thought I was:
1.) Is it possible to nest one struct within another in Clojure? (Judging from below that's a yes.)
2.) If so, what would be the correct syntax be? (Again, judging from below it looks like there are a few ways one could do this.)
3.) How do you fetch a value by a specified key when you've got a struct nested within another struct?
I guess my sample code didn't really demonstrate what I was trying to do very well. I'm adding this here so that others searching for this might find this question and its answers more easily.
I would agree with other posters in that struct maps don't really support inheritance. However, if you want to just make a new struct that uses the keys of another, this will work:
; Create the rect struct
(defstruct rect :height :width)
; Create the color-rect using all the keys from rect, with color added on
(def color-rect (apply create-struct (cons :color (keys (struct rect)))))
(defn create-color-rect
"A constructor function that takes a color and a rect, or a color height and width"
([c r] (apply struct (concat [color-rect c] (vals r))))
([c h w] (struct color-rect c h w)))
You don't need the echo-rect
function, you can simply evaluate the struct map instance to see what's in it:
user=> (def first-rect (struct rect 1 2))
#'user/first-rect
user=> first-rect
{:height 1, :width 2}
user=> (create-color-rect 249 first-rect)
{:color 249, :height 1, :width 2}
user=> (create-color-rect 249 1 2)
{:color 249, :height 1, :width 2}
You can make a struct be a value of another struct if you give it a key to be associated with. You could do it as below.
(You can easily access the guts of arbitrarily nested hashes/structs via ->
, as a bit of syntax sugar.)
(defstruct rect :height :width)
(defstruct color-rect :rect :color)
(def cr (struct color-rect (struct rect 1 2) :blue))
;; => {:rect {:height 1, :width 2}, :color :blue}
(:color cr) ;; => :blue
(:width (:rect cr)) ;; => 2
(-> cr :color) ;; => :blue
(-> cr :rect :width) ;; => 2
Nesting structures is possible and sometimes desirable. However, it looks like you're trying to do something different: It looks like you're trying to use inheritance of structure types rather than composition. That is, in form 2 you're creating a color-rect that contains a rect but you're trying to construct an instance as if it were a rect. Form 1 works because you're constructing c-rect1 from a pre-existing rect, which is the correct way to use composition.
A quick search on the Clojure group or just on the web in general should lead you to a good description of the distinction between composition and inheritance. In Clojure, composition or duck-typing (see Google again) is almost always preferred to inheritance.
Edit:
In answer to your Question #3: An alternative to using -> for extracting data in nested structures, as Brian Carper described in his answer, is get-in, along with its siblings assoc-in and update-in:
For example:
(def cr {:rect {:height 1, :width 2}, :color :blue})
(get-in cr [:rect :width])
;; => 2
(assoc-in cr [:rect :height] 7)
;; => {:rect {:height 7, :width 2}, :color :blue}
(update-in cr [:rect :width] * 2)
;; => {:rect {:height 1, :width 4}, :color :blue}
(assoc-in cr [:a :new :deeply :nested :field] 123)
;; => {:a {:new {:deeply {:nested {:field 123}}}},
;; :rect {:height 1, :width 2}, :color :blue}
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