user12341234
user12341234

Reputation: 7193

Clojure Multimethod dispatch on Java interface

Say I have a Java method that returns an object of some parent interface. The classes of the objects returned by this function are undocumented, however there is a rich and well documented hierarchy of interfaces all extending the parent interface. So for example:

public class Person {
   public IMeal favoriteMeal() { ... }
}

public interface IBreakfast extends IMeal { ... }
public interface ILunch extends IMeal { ... }
public interface IBrunch extends IBreakfast, ILunch { ... }

If I knew (and was confident in the stability of) the underlying objects, I could write a multimethod to dispatch on the various objects returned by that method:

(defmulti place-setting class)
(defmethod place-setting Omelet [meal] ...)

However, since only the interfaces are public, I'd rather dispatch on those. Is there a (good) way to dispatch on interfaces? Perhaps like:

(defmulti place-setting magic-interface-dispatch-fn)
(defmethod place-setting IBreakfast [meal] ...)

Upvotes: 2

Views: 306

Answers (2)

ClojureMostly
ClojureMostly

Reputation: 4713

This already works perfectly fine:

Note:

 public interface IFn extends Callable, Runnable
 public class Keyword implements IFn

And then:

(defmulti print-stuff class)
(defmethod print-stuff Callable [x] {:callable x})
(defmethod print-stuff :default [x] :not-found)
(print-stuff :foo) ;; => :callable

Note, multimethods always uses isa? internally on the (potentially custom) hierarchy. And (isa? Keyword Callable) is true.

Upvotes: 4

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91534

You can use the dispatch function of multimethods to map each interface to a "dispatch value" and then use these dispatch values to direct things to the correct code. This dispatch function defines in implicit hierarchy between the interfaces so you always end up with exactly one place the code needs to go.

hello.core> (defmulti foo (fn [meal] (condp instance? meal
                                       java.util.Collection ::breakfast
                                       java.lang.Double ::lunch)))
nil
hello.core> (defmethod foo ::breakfast
              [meal]
              (count meal))
#multifn[foo 0x2b6e7712]
hello.core> (defmethod foo ::lunch
              [meal]
              meal)
#multifn[foo 0x2b6e7712]
hello.core> (foo 2.3)
2.3
hello.core> (foo [1 2 3])
3

If defining the hierarchy in the dispatch function gets annoying, you can switch to using clojure's built in hierarchy feature to build these in arbitrary ways.

Upvotes: 0

Related Questions