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

haw*_*eye 24 haskell protocols clojure typeclass multimethod

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

现在,Haskell 对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
Run Code Online (Sandbox Code Playgroud)

现在Clojure中还有多方法 -所以你可以这样做:

(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
Run Code Online (Sandbox Code Playgroud)

同样在Clojure中你有协议 - 你可以用它来做:

(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
Run Code Online (Sandbox Code Playgroud)

现在这个人声称:

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

我的问题是:在Haskell中,Clojure中的协议和多方法对多态性的强大程度是什么?

Dan*_*zer 23

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

 class Foo a where
   bar  :: a -> ...
   quux :: a -> ...
   ...
Run Code Online (Sandbox Code Playgroud)

哪里a 必须是第一个参数.Haskell的类型类a出现在函数的任何地方.因此协议很容易表达,而不是类型.

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

根据我的知识,多方法无法做到的一些事情:

  1. 发送返回类型
  2. 在所有类型的类中存储多态的值 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是动态类型的,所以我会把它归结为两者之间的风格差异语言而不是某种方式的特定优势.当然,类型的开销也比多方法少得多,因为通常可以内联见证记录并且静态地解决所有问题.

  • @AndrewC不需要煽动关于打字的宗教辩论.无论你倾向于哪一方,都有好处. (9认同)
  • 问题:"我不想不必要地限制我的代码处理的数据类型." ......"我知道,我会使用动态打字." 现在你有八个问题.;) (4认同)
  • 如果你想聪明一点,你可以根据返回类型进行调度。您可以对函数进行调用,将返回类型作为参数,然后在多方法中对其进行分派。我的意思是,事实上,似乎多种方法的所有限制都只是因为没有可用的类型信息,但是没有什么可以阻止您在函数中请求它。Haskell 只是强制程序员始终指定类型(尽管推理使其变得微不足道)。所以我实际上并不认为 Clojure 多态性不够强大。 (2认同)

And*_*erg 8

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

class Read a where
  readsPrec :: Int -> String -> [(a, String)]
  ...
Run Code Online (Sandbox Code Playgroud)

使用多种方法显然不可能进行这种调度,这些方法必须根据其论点进行调度.

另请参阅我与普通OO的更广泛比较.