更广泛地说,这个问题是关于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中的类型类差异的原因是什么?
答案 0 :(得分:21)
显而易见的是,协议只能在第一个参数和第一个参数上进行调度。这意味着他们大致相当于
class Foo a where
bar :: a -> ...
quux :: a -> ...
...
a
必须成为第一个参数。 Haskell的类型类允许a
出现在函数中的任何位置。因此,协议很容易表达,而不是类型类。
接下来是多方法。多方法,如果我没有弄错的话,允许根据所有参数的函数进行调度。这在某些方面看起来比Haskell更具表现力,因为你可以不同地分派相同类型的参数。但是,这实际上可以在Haskell中完成,通常是通过将参数包装在newtype中进行调度。
根据我的知识,无法用多方法完成一些事情:
forall a. Foo a => a
要了解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。