实际上,我认为返回类型多态性是类型类的最佳功能之一。在使用了一段时间之后,有时我很难回到没有它的OOP样式建模。
考虑代数的编码。在Haskell中,我们有一个类型类Monoid
(忽略mconcat
)
class Monoid a where
mempty :: a
mappend :: a -> a -> a
我们如何将其编码为OO语言的接口?简短的答案是我们不能。这是因为类型mempty
是(Monoid a) => a
亦称,返回类型多态性。具有代数建模的能力对IMO非常有用。*
您从关于“参考透明性”的投诉开始您的帖子。这就提出了一个重要的观点:Haskell是一种面向价值的语言。因此,read 3
不必将类似的表达式理解为计算值的事物,也可以将它们理解为值。这意味着真正的问题不是返回类型多态性:它是具有多态类型([]
和Nothing
)的值。如果语言应该具有这些,那么它实际上必须具有多态返回类型才能保持一致。
我们应该说的[]
是类型forall a. [a]
吗?我认同。这些功能非常有用,并且使语言更加简单。
如果Haskell具有亚型,多态性[]
可能是所有人的亚型[a]
。问题是,我不知道没有空列表的类型是多态的编码方式。考虑一下如何在Scala中完成此操作(比在规范的静态类型化的OOP语言Java中进行的操作要短)
abstract class List[A]
case class Nil[A] extends List[A]
case class Cons[A](h: A. t: List[A]) extends List[A]
即使在这里,Nil()
也是Nil[A]
** 类型的对象
返回类型多态的另一个优点是,它使Curry-Howard的嵌入变得更加简单。
考虑以下逻辑定理:
t1 = forall P. forall Q. P -> P or Q
t2 = forall P. forall Q. P -> Q or P
我们可以在Haskell中将这些定理简单地捕获为定理:
data Either a b = Left a | Right b
t1 :: a -> Either a b
t1 = Left
t2 :: a -> Either b a
t2 = Right
总结一下:我喜欢返回类型多态性,只有在您对值的概念有限的情况下,才认为它会破坏参照透明性(尽管在特殊类型类情况下这种吸引力不那么明显)。另一方面,我确实找到了您关于MR的观点,并键入了默认强制性。
*。ysdx在评论中指出这并非完全正确:我们可以通过将代数建模为另一种类型来重新实现类型类。像java一样:
abstract class Monoid<M>{
abstract M empty();
abstract M append(M m1, M m2);
}
然后,您必须随身传递这种类型的对象。Scala有一个隐式参数的概念,它避免了显式管理这些事物的一些(但据我的经验)并非如此。将实用程序方法(工厂方法,二进制方法等)放在单独的F绑定类型上,事实证明这是一种使用支持泛型的OO语言来管理事物的非常好方法。就是说,如果我没有使用类型类对事物进行建模的经验,我不确定是否会搞怪这种模式,而且我不确定其他人也会这样做。
它也有局限性,开箱即用,无法获得实现任意类型的typeclass的对象。您必须显式地传递值,使用Scala的隐式值,或使用某种形式的依赖项注入技术。生活变得丑陋。另一方面,很高兴您可以为同一个类型具有多个实现。事物可以多种方式成为Monoid。此外,将这些结构分开携带会给IMO带来数学上更现代,更具建设性的感觉。因此,尽管我通常仍然更喜欢Haskell的方式,但我可能夸大了自己的论点。
具有返回类型多态性的类型类使这种事情易于处理。那不是最好的方法。
**。 约尔格·米塔格(JörgW Mittag)指出,这并不是在Scala中做到这一点的典型方法。相反,我们将使用类似以下内容的标准库:
abstract class List[+A] ...
case class Cons[A](head: A, tail: List[A]) extends List[A] ...
case object Nil extends List[Nothing] ...
这利用了Scala对底部类型以及协变量类型参数的支持。因此,Nil
类型Nil
不是Nil[A]
。在这一点上,我们离Haskell还很远,但是有趣的是注意到Haskell如何表示底部类型
undefined :: forall a. a
也就是说,它不是所有类型的子类型,而是所有类型的成员的多态(sp)。
还有更多的返回类型多态性。