与Haskell中的类型类相比,Clojure中的协议和多方法对多态性的影响力较小的原因是什么?

时间:2014-02-26 11:34:52

标签: haskell clojure protocols typeclass multimethod

更广泛地说,这个问题是关于expression problem的各种方法。我们的想法是,您的程序是数据类型和操作的组合。我们希望能够在不重新编译旧类的情况下添加新案例。

现在,Haskell对expression problem TypeClass提供了一些非常棒的解决方案。特别是 - 我们可以这样做:

class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool


member :: (Eq a) => a -> [a] -> Bool
member y [] = False
member y (x:xs) = (x == y) || member y xs

现在在Clojure中有multimethods - 所以你可以这样做:

(defmulti area :Shape)
(defn rect [wd ht] {:Shape :Rect :wd wd :ht ht})
(defn circle [radius] {:Shape :Circle :radius radius})
(defmethod area :Rect [r]
    (* (:wd r) (:ht r)))
(defmethod area :Circle [c]
    (* (. Math PI) (* (:radius c) (:radius c))))
(defmethod area :default [x] :oops)
(def r (rect 4 13))
(def c (circle 12))
(area r)
-> 52
(area c)
-> 452.3893421169302
(area {})
-> :oops

同样在Clojure中你有protocols - 你可以做到:

(defprotocol P
  (foo [x])
  (bar-me [x] [x y]))

(deftype Foo [a b c]
  P
  (foo [x] a)
  (bar-me [x] b)
  (bar-me [x y] (+ c y)))

(bar-me (Foo. 1 2 3) 42)
=> 45

(foo
 (let [x 42]
   (reify P
     (foo [this] 17)
     (bar-me [this] x)
     (bar-me [this y] x))))

=> 17

现在this individual makes the claim

  

但是,有协议和多方法。它们非常强大,但没有Haskell的类型类强大。您可以通过在协议中指定合同来引入类似类型的东西。这仅调度第一个参数,而Haskell可以调度整个签名,包括返回值。多方法比协议更强大,但没有Haskell的调度那么强大。

我的问题是: Clojure中的协议和多方法对于多态性的影响比Haskell中的类型类差异的原因是什么?

2 个答案:

答案 0 :(得分:21)

显而易见的是,协议只能在第一个参数和第一个参数上进行调度。这意味着他们大致相当于

 class Foo a where
   bar  :: a -> ...
   quux :: a -> ...
   ...

a 必须成为第一个参数。 Haskell的类型类允许a出现在函数中的任何位置。因此,协议很容易表达,而不是类型类。

接下来是多方法。多方法,如果我没有弄错的话,允许根据所有参数的函数进行调度。这在某些方面看起来比Haskell更具表现力,因为你可以不同地分派相同类型的参数。但是,这实际上可以在Haskell中完成,通常是通过将参数包装在newtype中进行调度。

根据我的知识,无法用多方法完成一些事情:

  1. 发送返回类型
  2. 在所有类型的类forall a. Foo a => a
  3. 上存储多态的值

    要了解1.如何发挥作用,请考虑Monoid它的值为mempty :: Monoid m => m。它不是一个功能,Clojure无法模拟这个功能,因为我们没有关于我们期望选择哪种方法的任何类型信息。

    对于2.考虑read :: Read a => String -> a。在Haskell中,我们实际上可以创建一个类型为[forall a. Read a => a]的列表,我们基本上推迟了计算,现在我们可以运行并重新运行列表的元素,尝试将它们作为不同的值读取。

    类型类也有静态类型,因此需要进行一些检查以确保您不会最终结束"卡住"没有静态调用的实例,但是Clojure是动态类型的,因此我将这两种语言的风格区别开来,而不是某种方式的特定优势。当然,类型的开销也比多方式少得多,因为通常可以内联见证记录并且所有内容都是静态解析的。

答案 1 :(得分:7)

最根本的区别在于,对于类型类,dispatch位于类型而不是。执行它不需要任何价值。这允许更一般的情况。最明显的例子是函数结果类型的(部分)调度。考虑例如Haskell的Read类:

class Read a where
  readsPrec :: Int -> String -> [(a, String)]
  ...

这种调度显然不可能通过多种方法进行,这些方法必须根据其论点进行调度。

另请参阅我更广泛的comparison with plain OO