December 21, 2014
Hot Topics:

Clojure's Approach to Polymorphism: Method Dispatch

  • April 26, 2010
  • By Amit Rathore
  • Send Email »
  • More Articles »

Ad-hoc Hierarchies

Now that we have the profit-rating infrastructure in place, we might want to expose the ratings to our members in a form similar to airlines membership status. We could treat bronze as an entry-level status, and members could graduate to silver and higher levels. We could also treat gold and platinum as premier statuses, affording such members a higher class of service. This classification might look like this:

Polymorphism in Clojure: Method Dispatch
Figure 5. Our domain demands a hierarchy of membership statuses. We can use this externally facing classification to simplify our code by defining an ad-hoc hierarchy reflecting this structure.

We can codify this hierarchy using Clojure's derive function, as follows:

(derive ::bronze ::basic)
(derive ::silver ::basic)
(derive ::gold ::premier)
(derive ::platinum ::premier)


Having done this, we can programmatically ensure it works as expected using the isa? function.

(isa? ::platinum ::premier)
true

(isa? ::bronze ::premier)
false


Since we now have a hierarchy, we can redefine the multimethod to calculate affiliate fee using it, as follows:

(defmulti affiliate-fee-for-hierarchy fee-category)
(defmethod affiliate-fee-for-hierarchy [:mint.com ::bronze] [user]
  (fee-amount 0.03 user))
(defmethod affiliate-fee-for-hierarchy [:mint.com ::silver] [user]
  (fee-amount 0.04 user))
(defmethod affiliate-fee-for-hierarchy [:mint.com ::premier] [user]
  (fee-amount 0.05 user))
(defmethod affiliate-fee-for-hierarchy [:google.com ::premier] [user]
  (fee-amount 0.03 user))
(defmethod affiliate-fee-for-hierarchy :default [user]
  (fee-amount 0.02 user))


This is even more succinct; it also captures the intent that acquiring a premier member from an affiliate partner is more expensive.

Java Class Hierarchy

While it is easy enough to create ad-hoc hierarchies as shown earlier, Clojure goes one step further to make things simple by providing support for Java classes out of the box. When Java classes are used as dispatch values, inheritance relationships are automatically respected, relieving the need for the hierarchy to be created again via calls to derive.

This ensures that both javax.swing.JComboBox and javax.swing.JFileChooser automatically treat javax.swing.JComponent as a parent. A method that uses the JComponent as a dispatch value will match if the dispatching function were to return either a JFileChooser or a JComboBox. The programmer doesn't have to do anything special for this to work.

The Visitor Pattern Revisited

Now that we know how multimethods solve dispatch problem, let's rewrite the AST program using multimethods. Imagine we represent a couple of syntax nodes thus:

(def aNode {:type :assignment :expr "assignment"})
(def vNode {:type :variable-ref :expr "variableref"})


We will use the appropriately named :type value of the hash-map to behave as the type upon which we will dispatch our multimethods. Here's the implementation of checkValidity:

(defmulti checkValidity :type)
(defmethod checkValidity :assignment [node]
  (println "checking :assignment, expression is" (:expr node)))
(defmethod checkValidity :variable-ref [node]
  (println "checking :variable-ref, expression is" (:expr node)))


And, here's the multimethod for generateASM:

(defmulti generateASM :type)
(defmethod generateASM :assignment [node]
  (println "gen ASM for :assignment, expr is" (:expr node)))
(defmethod generateASM :variable-ref [node]
  (println "gen ASM for :variable-ref, expr is" (:expr node)))


This is much simpler than having created the whole double dispatch mechanism in a language like Java or C++ that doesn't support it. In order to add new types of operations on the AST, we just create a new multimethod.

We've covered creation and usage of multimethods. Before closing our discussion, let's look at the situation where more than one dispatching value matches, which causes the multimethod to become confused.

Resolving Conflicts

Sometimes, a hierarchy may involve what looks like multiple inheritance. Consider the situation where a Programmer can be both an Employee and a Geek.

Polymorphism in Clojure: Method Dispatch
Figure 6. A hierarchy could lead to multiple inheritance. Clojure doesn't do anything to prevent this, but gives an elegant way to break a tie if multiple dispatch values match.

To reflect this, one would call derive with the following:

(derive ::programmer ::employee)
(derive ::programmer ::geek)


Such "multiple inheritance" relationships are perfectly valid in several domains, and Clojure doesn't stop the programmer from creating or using them. However, when methods are created with dispatch values of ::employee and ::geek, and the dispatch function returns ::programmer, the multimethod doesn't know which one to pick since they are both valid.

This multiple inheritance problem is by no means unique to Clojure. Languages like Java avoid it by disallowing classes from being derived from more than one parent class. In Clojure, we can break the tie by specifying our order of preference using the prefer-method function. Here's how we would specify that being a geek trumps being employed:

(prefer-method multimethod-name ::geek ::employee)


Read this as a preference of ::geek over ::employee.

We've covered the mechanics of multimethods and seen how they're a superior way of achieving polymorphism than what languages limited to single dispatch can ever provide. Having said that, multimethods aren't used very much, mostly because the functional programming style makes this kind of polymorphism less needed. When there is a need, however, they can be a very elegant solution.

Summary

As we have seen, what is generally referred to as polymorphism in the context of languages such as Java and C++, is actually a rather specific variety called subtype polymorphism. Further, the implementation in such languages is limited to single dispatch, which is again a very specific case of the far more general possibility of multiple dispatch.

Recall that these languages subscribe to a view of OOP that demands methods (and data) belong to the enclosing class. It is probable that this is the reason these languages support only single dispatch -- they only dispatch on the receiver since it is the class that owns the potentially polymorphic method.

In Clojure, however, a multimethod doesn't belong to anything. The concrete methods, instead, belong to the multimethod, and they can be dispatched based off any number of types. In fact, the dispatch function is just an ordinary function written by the programmer and can do anything it wants. It is by no means limited to only the type of the arguments, opening up possibilities that can probably not even be dreamed off in other languages.

About the Author

Amit Rathore has 10 years of software development experience, building mission-critical enterprise systems. He's the chief architect at runa.com, a targeted dynamic sale pricing service to online merchants. The system, written entirely in Clojure, collects extensive click-stream data from consumers browsing products, analyzes it using statistical models, and then offers the consumers special, personalized pricing in real-time. The system must handle extremely high-loads, and is written in a highly distributed, fault tolerant manner ideal for Clojure.





Page 3 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel