views:

954

answers:

4

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.

+1  A: 

I am really new to clojure, so I might be wrong. But I think, you can't do something like

(defstruct color-rect :color (struct rect))

As far as I understand clojure-structs, this would create a struct (basically a map with known keys), that has somehow the struct 'rect' as one of it's keys.

My assumption is backed by the observation that a simple evaluation of (struct rect) yields

{:height nil, :width nil}

Whereas an evaluation of (struct color-rect) yields:

{:color nil, {:height nil, :width nil} nil}

EDIT: What could help you is the fact, that structs are not limited to the keys, they are defined with. It appears as if you could accomplish, what you are trying by something like this:

(def c-rect1 (struct-map color-rect :color 249 :height 1 :width 1 )) ;form 3
Mo
This is correct: You're using an empty rect struct as the key for one of color-rect's values. It would make more sense to use something like :rect.
Nathan Kitchen
+5  A: 

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}
Nathan Kitchen
+6  A: 

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}
pjb3
Thanks Paul--this is pretty much exactly what I wanted to know.
Onorio Catenacci
+5  A: 

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
Brian Carper