Search code examples
clojureinstanceofdouble-dispatchmultimethod

How is an "isa?" based multimethod more than syntax sugar for instanceof?


I am taking an example from the clojure site.

(defmulti foo class)
(defmethod foo ::collection [c] :a-collection)
(defmethod foo String [s] :a-string)

(foo [])
:a-collection

(foo (java.util.HashMap.))
:a-collection

(foo "bar")
:a-string

This functionality is cool. I'm trying to understand the technical reasons why this is superior to an instanceof based implementation in (for example) java. It seems to me essentially equivalent in function with nicer syntax.

public <T> String foo(final T t) {
    if (t instanceof Collection) {
        return "a-collection";
    } else if (t instanceof String) {
        return "a-string";
    } else {
        throw new IllegalArgumentException("Undefined - no available dispatch");
    }
}

What are the reasons why multimethods are considered a great alternative to visitor pattern based double dispatch while instanceof is not when they seem like they're essentially doing the same thing?


Solution

  • One of the benefits discussed in the comments is that the defmulti and defmethod can be done in different files, by different users. An excellent example is Clojure's own print-method multi-method.

    From the docs, we see how we can define a custom print-method for a new record type we create:

    (deftype XYZ [])
    
    ; without custom print-method defined:
    user=> (prn (XYZ.))
    #<XYZ user.XYZ@2670d85b> 
    
    ; Note, this hooks into the pre-existing `(defmulti print-method ...)`
    (defmethod print-method XYZ [v ^java.io.Writer w] 
      (.write w "<<-XYZ->>"))
    
    ; with print-method
    user=> (prn (XYZ.))
    <<-XYZ->>
    

    So while it has similarity to a giant cond statement, it is more flexible & cleaner.