【问题标题】:Clojure mutimethods for dispatch by type按类型分派的 Clojure 多方法
【发布时间】:2016-04-05 22:47:17
【问题描述】:

我在 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 互操作。我想这样做是因为更好地理解 clojure 中的所有这些“java 东西”。看起来他们很容易,但实际上有很多隐藏的障碍。所以,我写道:

(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 解决。如果我错了或遗漏了什么 - 请向我解释.. ;)

【问题讨论】:

  • 只是一些风格提示:将square函数提取到外部,在每个递归步骤中通过letfn在本地定义它是没有用的。另外,看看recur(可能还有loop)并将递归调用移到尾部调用位置——你进行递归的方式可能很快就会破坏堆栈。

标签: java clojure clojure-java-interop multimethod


【解决方案1】:

如果您想根据多个参数的类型进行调度,那么多方法是合适的工具。请记住,这已经内置在所有提升数学函数中,例如 +' 和 *'。

user> (+' 2N 3)
5N

在 clojure 中,您不能使用类型提示来完成这种类型的重载/调度(这是故意的)。如果您仅基于第一个参数进行调度,则应使用protocols,因为它们要快得多

【讨论】:

  • 好的,谢谢,但是为什么这么复杂呢?我的意思是,按类型分派不是已经在 J​​VM 中实现了吗?是不是因为 clojure 中的每个函数看起来都像是 JVM 的一个类?
  • 虽然语言是这样设计的,但我无法说出个人动机。 Multimethods 提供了一个完全通用的调度方法,它涵盖了这种以及可以在调用时表达的所有其他类型的调度。协议通过第一个参数提供调度,这是绝大多数情况。所以我只能假设第二个参数的按类型分派没有被视为重要到足以保证第三个分派类型(以及后来添加的协议,最初 Clojure 只有多方法)。如果您愿意,可以使用宏构建此功能
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-25
相关资源
最近更新 更多