Dynamic mixins in Clojure: an experiment.

My web-friend Debasish Ghosh recently come up with an excellent post about modeling the same domain with Scala and Clojure, focusing in particular on how to implement dynamic behavior through Scala mixins and Clojure combinators.

Now, while the post is excellent as always, and Clojure combinators are pretty cool, I think a domain model is better represented with Clojure records and protocols, so let me show you a little experiment about how to implement dynamic mixins in Clojure.
This is my first Clojure-related blog post, and I'm no way a Clojure expert, so take it with care and feel free to hand me any kind of feedback ;)

First, for those unfamiliar with records and protocols, there are a few good articles around: to sum up in a single sentence, protocols are a way to describe a contract made up of functions you have to implement in order to adhere to the protocol itself, while records (and types) define data and implement protocols.
So, let's define a Person record and a simple Outputter protocol for outputting things:

(defrecord Person [name])

(defprotocol Outputter
(output [self logic])
)

The record defines only the person name, and the protocol defines an output function whose actual execution logic is plugged from the outside through the logic argument.

The most common way to implement a protocol is to extend a record type and implement the proper behavior:

(extend-type Person
Outputter
(output [self logic] (println (:name self)))
)

Glancing through the code, we're extending the Person record and implementing the Outputter by printing out the person name.
But there's a problem with that: the behavior is static. More specifically:

  • You can't dynamically remove protocol implementations.

  • Added protocol implementations are shared by all record instances, so you can't compose protocol implementations with specific record instances.


Let's try to come up with a solution.

First, let's define the function that is at the core of our experiment: the mix function.

(defn mix [source mixins]
(let [m (merge source mixins)] #(get m %1))
)

Very short and concise, isn't it? We'll come back to it in a moment.

Then, let's define a simple record instance and two protocol implementations (without extending any record this time): we'll call them, guess what, traits.

(def joe (Person. "joe"))

(deftype OutputterTrait [target]
Outputter
(output [self logic] (logic target))
)

(deftype DummyTrait []
Outputter
(output [self logic] (println "ignored external logic"))
)

We defined a simple record with no protocols implemented, and two separated protocol implementations.

Now, let's mix them up!

(def mixed (mix joe {:outputter (OutputterTrait. joe) :dummy (DummyTrait)}))

What's up?
We just used our previous mix function to dynamically associate joe with two different protocol instances, each one identified by a particular keyword.
Now, we can output our mixed Joe calling both protocol implementations:

(output (mixed :outputter) #(println (:name %1)))
(output (mixed :dummy) #(println "this will be ignored"))

The trick is the mix function that dynamically associates the record instance with protocol implementations identified by keyword, later used to recall the protocol back.

We've got dynamic mixins so, but at the cost of depending on an "external" keyword we have to be careful about: passing the wrong keyword may in fact lead to the wrong protocol implementation.

So, always prefer the classical, built-in way to use protocols and records ... but feel free to experiment with dynamic mixins when needing dynamic behavior!

Any feedback will be highly appreciated ... enjoy and see you next time!

21 comments:

Javier said...

great "experiment", i think is totally usable and idiomatic. I tried to mimic the scala way of related post (this code) but one diff was the static bind between protocol and record as you have pointed, althoug adding another level of indirection associate outer functions in protocol impl.
Have you tried tou use reify? it could to be a half way to add behaviours less dynamic than map but more than with extend-type.

Sergio Bossa said...

Hi Javier,

thanks for your interest and kind words!

Regarding "reify", yes, I thought about it and you could actually use it in my example to directly implement the protocols, rather than explicitly defining a type (as I did mostly for clarity).
But I don't see how could "reify" replace the map association: may you elaborate on that?

Javier said...

In the gist linked in my prev comment i've added a reify aproximation, but it doesnt replace the map with mixins associated with keys and types as traits, it is a different way for learning and experimenting and i dont know if the workaround has sense.
With your example the code'd be:

(with-outputter [j] (reify Outputter (output [_ logic] (logic j)))
(output (with-outtputter joe) #(println (:name %1)))

The bind is make with fn call with a closure and dont change joe out of output call (this can be useful or not), and i think maybe it could be applied for adapting old data and fns to new protocols.

Sergio Bossa said...

Your code makes certainly sense, in particular if you have to bind an existent record to a limited set of protocols.
But, it's still static: the with-x function strictly refers to a particular protocol, and you can't mix several protocols this way.

Or did I miss something?

Thanks for sharing your thoughts!

Javier said...

Certainly it's not dynamic, maybe more closed to scala. You have to redefine the function with-x (or the function called in it) if you wants to change the behaviour. I've only added more indirections between the data and the protocol.
I think being clojure dynamic per se your solution fits well, but maybe the mixing could live in metadata of Joe (where clojure puts the info about types) and no with its values.

Sergio Bossa said...

The idea of putting the "traits map" into the object metadata sounds very good indeed: I've never found a use case to play with clojure metadata map, so I'll give it a try.

Thanks for sharing your thoughts and the nice discussion!

nickikt said...

nice post.

would be cool if i could track your blog on http://planet.clojure.in/.

Sergio Bossa said...

Thanks for your kind words nickickt!
Obviously, feel free to aggregate my blog on http://planet.clojure.in/ ... cheers!

Alex Ott said...

Sergio - I added your blog to Clojure planet several days ago

Andy said...

I believe getting clojure vibrant every se your option fits well
When we play the WOW, we need to try get the Cheap WOW Gold,thst's to say, spend less money, do we have any good way to Buy World Of Warcraft Gold from trust friends or some way else? When we have that we can play the game becomes more quickly and update the levels more easy.

The Latest News said...

Thank you for sharing. Glad to see you.It is really a good post.Cheap Eden Gold

gamefan said...

I'm so pleased for having the 'next' button on my page.
RS Gold
Buy RS Gold
Cheap RS Gold
Buy Runescape Gold

catriona said...

Very informative blog... keep sharing.





ipad casino

catriona said...

Very nice information for us you have shared... keep going...




iphone poker

catriona said...

Intresting layout on your blog. Best wishes for you future blogging career... Thanks for sharing a very useful information.....



ipad blackjack

catriona said...

I totally agree with what you had to say.... That is wonderful.....





ipad bingo

catriona said...

Thanks for sharing such useful information. It seems that you did a lot of hard work for this article. Keep it up.




ipad slots

catriona said...

Keep blogging with new post! Unique and useful to follower..... keep it up... well done....




ipad roulette

designer wedding dresses said...

Thanks for the informative writing. Would mind updating some good tips about it. I still wait your next place.An offbeat from tradition and formality. simple wedding dresses are relatively less considered compared with common cascading ones. Yet there’s always a need to break away from the norm for a little while. Amber’s wedding dresses 2012 favor you an untilled land to show off your talent and philosophy.

Stacey said...

Wonderful blog! I found it while browsing on Yahoo News. Do you have any suggestions on how to get listed in Yahoo News? Regards - BlackBerry Application Development

Flower Girl Dresses said...

Once again great post. You seem to have a good understanding of these themes.When I entering your blog,I felt this .

Come on and keep writting your blog will be more attractive. To Your Success!

Classic Dresses
Classic Bridesmaid Dresses
Wedding Dresses with Sleeves