mixins/multiple inheritance

215 views
Skip to first unread message

Razvan Rotaru

unread,
Feb 5, 2012, 4:15:26 PM2/5/12
to Clojure
Hi,

I found some posts about this topic, but they did not clarify things
in my head well enough, so I have to start my own... :)

I'm basically craving for multiple inheritance or mixins, at least
with my current way of thinking. I haven't really gone deep enough
with multimethods or protocols, so I might be a little off track by
looking for multiple inheritance.

Let's take an example:

I want to implement some gui widgets, so there's a main method
"render" which draws the widget on the screen. I want to have two
types:

- Textfield
- Datetimepicker

Each can be "mixed" with the Label widget, so that we have:

- LabelledTextfield
- LabelledDatetimepicker

So, two challenges occur:
1/ the render method for Label needs to call the render method for
it's "mixed" widget (either Textfield or Datetimepicker) - let's
assume that labels are rendered "around" the actual widget, so that we
have a simple way to mix the implementations of render method
2/ the Label widget comes with its own data (the label text)

For the second thing, I think it will work with defrecords, since the
label can be a map entry which can be stored in the actual widget,
even if it's not mixed with Label. I still want to point this out in
case there are other ways of achieving this.

How can I achieve 1? Are there alternatives for 2?

Thanks for reading,
Razvan

Matthias Diehn Ingesman

unread,
Feb 6, 2012, 5:25:49 AM2/6/12
to Clojure
It sounds like a use for the decorator pattern, or am I missing
something? In case I am not I have given it a shot using records and
protocols:

(defrecord TextField [text])
(defrecord DatetimePicker [datetimes])
(defrecord Label [text widget])

(defprotocol Renderable
(render [this]))

(extend-protocol Renderable
TextField
(render [this]
(str "[" (:text this) "]"))

DatetimePicker
(render [this]
(apply str (:datetimes this)))

Label
(render [this]
(str (:text this) ": " (render (:widget this)))))

(comment
(def textfield (TextField. "Foo"))
(def datetimepicker (DatetimePicker. [:yesterday :today :tomorrow]))
(def labeledtextfield (Label. "Name" textfield))
(def labeleddatetimepicker (Label. "Date" datetimepicker))
(render textfield) ;=> [Foo]
(render datetimepicker) ;=> :yesterday:today:tomorrow
(render labeledtextfield) ;=> Name: [Foo]
(render labeleddatetimepicker) ;=> Date: :yesterday:today:tomorrow
)

1/ is handled by the Renderable implementation of Label calling the
Renderable implementation of whatever widget is decorated by the Label
instance.

2/ is handled by having the label decorator as a record.

Regards,
Matthias

Matthias Diehn Ingesman

unread,
Feb 6, 2012, 5:20:58 AM2/6/12
to Clojure
It sounds a little like the decorator pattern would do the trick for
you in this case - am I missing something? In case I am not, I have
1/ is handled by the Renderable implementation for Label calling the
Renderable implementation on whatever widget it decorates with a
label.

2/ is handled by having the Label decorator widget as a record.

Regards,
Matthias

On 5 Feb., 22:15, Razvan Rotaru <razvan.rot...@gmail.com> wrote:

Razvan Rotaru

unread,
Feb 6, 2012, 2:22:43 PM2/6/12
to Clojure
Thanks,

This works, but there's a problem: the labeledtextfield is not a
textfield anymore, it's a label. Therefore it does not behave like a
textfield (which implements other protocols as well). I need multiple
inheritance, in one way or another. I've been trying to find a way to
implement with multimethods.

For example, if label was a mixin, then it means I could do something
like this:

(def textfield (new TextField "Foo")
(def labeledtextfield (new (mix TextField Label) "Name" "Foo"))

(render textfield) ;=> [Foo]
(render labeledtextfield) ;=> Name: [Foo]

Oh, and I'd like to stay away from macros, if possible. :)

Razvan

On Feb 6, 12:20 pm, Matthias Diehn Ingesman <matdi...@gmail.com>
wrote:

Matthias Diehn Ingesman

unread,
Feb 8, 2012, 4:12:37 AM2/8/12
to clo...@googlegroups.com
Hm, I wrote a reply yesterday figuring it would show up today (seeing as I accidentally double posted last time because it did not show up at once), but that reply seems to be lost.

I see what you mean now, I think. Seeing as you will have to build your hierarchies yourself you might try something like the following:

(defrecord Label [text])
(defrecord TextField [text])

(defmacro c-mix [orig o-args mixin m-args]
  `(with-meta
    (new ~orig ~o-args)
    {:mixin (new ~mixin ~m-args)}))

;; Extracting the mixin instance
(defn mixin [o]
  (:mixin (meta o)))

(defmulti render (fn [x] (or (class (:mixin (meta x)))
                                     (class x))))
(defmethod render TextField [x] (:text x))
(defmethod render Label [x] (str (:text (mixin x)) ": " (render (with-meta x {}))))

(def labelledtextfield (c-mix TextField "Foo" Label "Name"))

(def textfield (TextField. "Foo"))

(render textfield) ;=> "Foo"
(render labelledtextfield) ;=> "Name: Foo"

This will allow you to emulate dynamic mixin composition by using the c-mix macro which is like a constructor call that attaches an instance of the mixin object as meta data. I know you would like to avoid macros, but since it is not possible to instantiate an object from a function parameter I don't think you can avoid them entirely.

Ad-hoc hierarchies using derive might also combine well with this solution, but then you would have to stay away from defrecord and define your own types I think.

Regards,
 Matthias

Cedric Greevey

unread,
Feb 8, 2012, 8:49:32 PM2/8/12
to clo...@googlegroups.com
Is there a reason not to just use protocols for your mixins?

(defprotocol Labelled
whatever)

(defprotocol Textfield
whatever-else)

(defprotocol Datetimepicker
something)

(defrecord Label
Labelled
whatever ...)

(defrecord Textbox
Textfield
whatever-else ...)

(defrecord LabelledTextbox
Labelled
whatever ...
Textfield
whatever-else ...)

and so forth, and probably punt to external fns for anything that
would otherwise get duplicated between Textbox and LabelledTextbox, or
between Label and LabelledTextbox, and so forth.

Alex Baranosky

unread,
Feb 9, 2012, 1:24:55 AM2/9/12
to clo...@googlegroups.com
Cedric's is the approach I would take.  Like he said, use private functions for the shared code.


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Matthias Diehn Ingesman

unread,
Feb 9, 2012, 3:53:51 AM2/9/12
to clo...@googlegroups.com
The problem with the protocols approach is that it leads to an explosion in the number of explicitly defined records. His example was only with labels, but I can imagine he might have borders and any number of other decorations on the widgets, in which case he would need something like

(defrecord Textfield ...)
(defrecord LabelledTextfield ...)
(defrecord BorderedTextfield ...)
(defrecord LabelledAndBorderedTextfield ...)
; and so on for each combination of decorator and widget

With the other approach he would only need a record for each widget and one for each decorator.

David Nolen

unread,
Feb 9, 2012, 9:28:52 PM2/9/12
to clo...@googlegroups.com
I think the suggestions thus far are not ideal. Both protocols and multimethods provide a way to define a default case. The default case gives you opportunity to adopt a delegation / component based design.

(defprotocol IFlightOrgan
  (flight-organ [this]))

(defprotocol IFlight
  (can-fly? [this])
  (fly [this] [this body]))

(extend-type Object
  IFlight
  (can-fly? [this]
    (satisfies? IFlightOrgan this))
  (fly
    ([this]
       (fly (flight-organ this) this))
    ([this body]
       (fly (flight-organ this) body))))

Now any type that provides a flight-organ can fly (note we even handle the self-sufficient case). How the flight organ is acquired is completely up to the provider - it could be a field or it could done via map lookup (if you need the type to be the aggregate of many components).

This paper is really worth reading, "Organizing Programs Without Classes" http://cs.au.dk/~hosc/local/LaSC-4-3-pp223-242.pdf

David

Reply all
Reply to author
Forward
0 new messages