Clojure mutimethods按类型发送

时间:2016-04-05 22:47:17

标签: java clojure clojure-java-interop multimethod

我在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)))))

现在我想玩类型提示和java互操作。我想这样做是为了更好地理解所有这些" java的东西"在一个clojure。它看起来很容易,但实际上存在很多隐藏的障碍。所以,我写道:

(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))))))

当然,由于错误的arity问题,它甚至没有编译。所以我谷歌如何按类型发送clojure并找到multimethods。事实上,在这一点上,我变得天真和兴奋,我以前从未尝试multimethods,所以,我写了类似的东西:

(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)))

事情变得有点复杂。这工作正常,但我想知道是否有一种更干净的方式,只需按类型重载。我没有理解为什么在clojure中我不能这样做,即使在Java中我也可以。

PS:当然我可以"隐藏"基于嵌套函数内部java.math.BigInteger的逻辑,并从外部函数fast-pow调用它,但实际上 - 对我来说它并不感兴趣,我很确定,这个问题可以通过使用来解决multimethods。如果我错了或遗失了某些东西 - 请向我解释..;)

1 个答案:

答案 0 :(得分:3)

如果要根据多个参数的类型进行调度,那么multimethods是合适的工具。请记住,这已经包含在所有促进数学功能中,例如+'和*'。

user> (+' 2N 3)
5N

你不能在clojure中使用类型提示来完成这种类型的重载/调度(这是故意这样做的)。如果您只根据第一个参数进行调度,则应使用protocols,因为它们很多更快。