Follow @dstiberova

Multimethods and Protocols

October 16, 2015

This time I'm trying to understand protocols. To be honest, I've never heard about protocols till today, but I want this knowlege, so let's start. 😃

Why we need Multimethods and Protocols?

In Clojure, and in functional languages in general, there are multiple ways to adapt functions to various types (in our case it will be animals). If we need to adapt function to various types, we usually use conditions (if, cond..), and if we want to add new behavior for another type, we need to touch part of code where types are distinguished.

I have a function

Results

and I decided to find out something about Pumpkin and adopt him

I need to add a map for pumpkin, into map of animals and an 'animal language' into tell-me-about-animal function (this animal sound is located in this function because all cats have the same language, so it doesn't make sense to write it with every new cat, or dog .. )

Result


Multimethods

In this case, using multimethods makes our code more extensible, because when adding new animal type, we don't need to touch the tell-me-about-animal core function, which could be for example located in a library, as such it would be difficult/impossible to modify it. To add a new animal type, we just need to add a new case to our multimethod (defmethod).

Results

defmulti creates a new multimethod with dispatch function. In our case we create multimethod with name animal-sound and dispatch function in this multimethod selects keyword :species from argument.

defmethod creates and installs new method for mutimethod associated with dispatch value. defmethod animal-sound creates new method for (defmulti animal-sound (fn [my-animal] (:species my-animal)) .

We created three methods:

first method

is called when returned value from defmulti animal-sound dispatching function is :cat

second method

is called if when returned value does not match any of the others methods.

example

if we call (animal-sound {:name "Twiggi" :species :dog :home "house" :age 3}) the dispatch function returns value under keyword :species , which is :dog , and the appropriate method is used. Which means returned value will be "Woof"

Result


Protocols

Because protocols are faster than multimethods, we use protocols when they are sufficient. It is used, when you need to dispatch only on type. We still can use multimethods, when the dispatching logic gets more complicated. So in our example it's better to use protocols, because we need to dispatch only on type.

Create protocol

Defprotocol takes a name and optional docstring. Next there are the method signatures. Method signatures contain method name (sound) argument specification ([this]) and an optional docstring ("animal language") . When we want to implement protocol in any way, we always need to implement all protocol methods.

reify creates a unique anonymous type. It is useful when you need to create single implementation of protocol, which doesn't have a named type.

we created one anonymous instance, which implements protocol IAnimal with all protocol methods.

On this instance yeti we can call all protocol methods.

Result

deftype creates a named type which implements a protocol.

deftype Goat creates new type which can take arguments (in our example arguments field is empty, because we don't use them) ,followed by protocol name which we want to implement (IAnimal) and implementation of all protocol methods.

i.e. (sound [this] "Meh") is protocol implementation for IAnimal method (sound [this] "animal language")

(def Kozenka (Goat.)) constructs a new instance of type Goat.

Result

Now we can call method sound on instance Kozenka

defrecord creates a named type which implements protocol and additionaly behaves like map for any arguments passed to constructor (where map keys are argument names, and map values are actual arguments passed when calling a constructor)

We defined new record of type Cat , with arguments name home age . For this record we implemented IAnimal with all protocol methods.

Then we defined new instance of type cat assigned to Siggi , with arguments "Siggy" "House" 1

When we call Siggi , arguments "Siggi" "House" 1 are passed as values for keywords name home age as defined in defrecord Cat .

Now we can call all methods (sound, species) on instance Siggi

Result

You can work with them like with maps. You can call assoc, dissoc, keys e.t.c on them.

Take a look at our example with protocols

Results

And now we add pumpkin

We just added pumpkin into my-animals map and created new defrecord for species pig.

Result

I hope this post helped you understand protocols and multimethods. It was not easy journey for me, but I think I did it 😃 If you see some incorrect informations or you want to tell something more, or ask someting, feel free to write a comment 😃