Search code examples
javaclojureclojure-java-interopmultimethod

Clojure mutimethods for dispatch by type


I implemented fast power algorithm in clojure:

(defn fast-pow [a n]
  (cond (zero? n) 1
    (even? n) (letfn [(square [x] (*' x x))]
                (square (fast-pow a (/ n 2))))
    :else (*' a (fast-pow a (dec' n)))))

And now I want to play with type hints and java interop. I want to do this because of better understanding all this "java stuff" in a clojure. It's looks like their are pretty easy, but in fact there is a lot of hidden obstacle. So, I wrote:

(defn ^java.math.BigInteger fast-pow 
  ([^java.lang.Long a ^java.lang.Long n]
   (fast-pow (java.math.BigInteger/valueOf a) (java.math.BigInteger/valueOf n)))
  ([^java.math.BigInteger a ^java.math.BigInteger n]
   (cond (zero? n) java.math.BigInteger/ONE
         (even? n) (letfn [(square [x] (.multiply x x))]
                    (square (fast-pow a (.divide n (java.math.BigInteger/valueOf 2)))))
         :else (.multiply a (fast-pow a (.subtract n BigInteger/ONE))))))

Of course it's not even compiled, because of wrong arity problem. So I google how to dispatch by type in clojure and found multimethods. In fact at this point I was vary naive and excited, I never tried multimethods before, so, I wrote something alike:

(derive java.math.BigInteger ::biginteger)
(derive java.lang.Long ::long)
(defmulti fast-pow (fn [a n] [(class a) (class n)]))

(defmethod fast-pow [::biginteger ::biginteger] [a n]
  (cond (zero? n) java.math.BigInteger/ONE
        (even? n) (letfn [(square [x] (.multiply x x))]
                    (square (fast-pow a (.divide n (java.math.BigInteger/valueOf 2)))))
        :else (.multiply a (fast-pow a (.subtract n BigInteger/ONE)))))
(defmethod fast-pow [::long ::long] [a n] 
  (fast-pow 
   (java.math.BigInteger/valueOf a) 
   (java.math.BigInteger/valueOf n)))

Things are going a bit complicated. That works fine, but I wonder is there a more clean way to such thing as just overload by type. I didn't get why in clojure I cannot do that even if in Java I can.

PS: Of course I can "hide" logic based on java.math.BigInteger inside an nested function, and call it from outer function fast-pow, but in fact - it's not interesting for me, I pretty sure, this issue could be solved by using multimethods. If I'm wrong or missing something - please explain it to me.. ;)


Solution

  • If you want to dispatch based on the type of more than one argument then multimethods are the appropriate tool. keep in mind that this is already built into all the promoting math functions such as +' and *'.

    user> (+' 2N 3)
    5N
    

    You cannot, in clojure, use type hints to accomplish this type of overloading/dispatch (and this was done on purpose). If you are only dispatching based on the first argument you should use protocols because they are much faster.