Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure Structure Nested Within Another Structure

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.

like image 455
Onorio Catenacci Avatar asked Feb 16 '09 23:02

Onorio Catenacci


3 Answers

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}
like image 171
pjb3 Avatar answered Oct 31 '22 23:10

pjb3


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
like image 39
Brian Carper Avatar answered Nov 01 '22 00:11

Brian Carper


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}
like image 6
Nathan Kitchen Avatar answered Oct 31 '22 23:10

Nathan Kitchen