Polymorphism with Multimethods in Clojure
Jan 20, 2017
2 minutes read

I love birds, especially chickens. They wander and look for food all the day. They eat pernicious bugs, leave precious droppings, and they lay eggs almost each day. Also there are more animals which lay eggs too. The number of the eggs and the weight of them depends on the specie. The methods below calculate the total weight according to parameters, season and sub-specie. All of them are just examples to understand multimethods in Clojure for sure.

Namespaced keywords used to stay in this namespace.

(defmulti lay-egg ::specie)

(defmethod lay-egg ::chicken [m]
  (* 200 50 (::season m) (::sub-specie-rate m 1)))

(defmethod lay-egg ::quail [m]
  (* 100 15 (::season m) (::age-rate m 1)))

(defmethod lay-egg ::goose [m]
  (* 50 150 (::season m) (::color-rate m 1)))

(lay-egg {::specie ::chicken
          ::season 1})
;=10000
(lay-egg {::specie ::goose
          ::season 2})
;=15000

What if we want to calculate for an another specie?

(lay-egg {::specie ::budgerigar
          ::season 1})
;IllegalArgumentException No method in multimethod 'lay-egg' for dispatch value: :user/budgerigar  clojure.lang.MultiFn.getFn (MultiFn.java:156)

It’s possible to define a default method to prevent this case. :default is a special keyword.

(defmethod lay-egg :default [m]
  (* 1 1 (::egg m 1)))

(lay-egg {::specie ::budgerigar
          ::season 1})
;=1

If we want to use the same method for the sub-species, we need to derive from the root specie.

(derive ::grand-chicken ::chicken)

(lay-egg {::specie ::grand-chicken
          ::sub-specie-rate 3
          ::season 1})
;=30000

(derive ::white-chicken ::chicken)
(isa? ::white-chicken ::chicken)
;=true

(derive ::white-grand-chicken ::grand-chicken)
(derive ::white-grand-chicken ::white-chicken)
(isa? ::white-grand-chicken ::chicken)
;=true
(lay-egg {::specie ::white-grand-chicken
          ::sub-specie-rate 5
          ::season 1})
;=50000

Let’s assume that duck is the product of a secret night between goose and chicken.

(derive ::duck ::chicken)
(derive ::duck ::goose)

(lay-egg {::specie ::duck
          ::season 1})
;IllegalArgumentException Multiple methods in multimethod 'lay-egg' match dispatch value: :user/duck -> :user/goose and :user/chicken, and neither is preferred  clojure.lang.MultiFn.findAndCacheBestMethod (MultiFn.java:178)

It’s not possible to dispatch because there two suitable methods. One of them should be preferred.

(prefer-method lay-egg ::goose ::chicken)

(lay-egg {::specie ::duck
          ::season 1})
;=7500

The examples can be extended. It’s possible to define hierarchies with make-hierarchy and modify them with alter-var-root. Also there more handy methods like parents, descendants, ancestors etc.


Back to posts


comments powered by Disqus