(ML) 模块与 (Haskell) 类型 类

(ML) Modules vs (Haskell) Type Classes

根据 Harper (https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/), it seems that Type Classes simply do not offer the same level of abstraction that Modules offer and I'm having a hard time exactly figuring out why. And there are no examples in that link, so it's hard for me to see the key differences. There are also other papers on how to translate between Modules and Type Classes (http://www.cse.unsw.edu.au/~chak/papers/modules-classes.pdf) 的说法,但这与程序员的角度来看实际上并没有任何关系(它只是说没有一个人可以做另一个人可以做的事情) '模仿)。

具体来说,在 first link:

The first is that they insist that a type can implement a type class in exactly one way. For example, according to the philosophy of type classes, the integers can be ordered in precisely one way (the usual ordering), but obviously there are many orderings (say, by divisibility) of interest. The second is that they confound two separate issues: specifying how a type implements a type class and specifying when such a specification should be used during type inference.

我也不明白。一个类型可以在 ML 中以不止一种方式实现一个类型 class?在不创建新类型的情况下,如何通过示例对整数进行整除排序?在 Haskell 中,您必须执行一些操作,例如使用数据并让 instance Ord 提供替代排序。

而第二个,两者在Haskell中不是不同的吗? 指定 "when such a specification should be used during type inference" 可以通过这样的方式完成:

blah :: BlahType b => ...

其中 BlahType 是在类型推断期间使用的 class 而不是实现 class。而 "how a type implements a type class" 是使用 instance 完成的。

谁能解释一下 link 真正想说的是什么?我只是不太明白为什么 Modules 会比 Type 类.

限制更少

要了解文章的内容,请花点时间考虑 Haskell 中的 Monoid 类型 class。幺半群是任何类型 T,它具有函数 mappend :: T -> T -> T 和标识元素 mempty :: T,以下内容适用。

a `mappend` (b `mappend` c) == (a `mappend` b) `mappend` c
a `mappend` mempty == mempty `mappend` a == a

有许多 Haskell 类型符合此定义。立即想到的一个例子是整数,我们可以为其定义以下内容。

instance Monoid Integer where
    mappend = (+)
    mempty = 0

您可以确认满足所有要求。

a + (b + c) == (a + b) + c
a + 0 == 0 + a == a

事实上,这些条件适用于所有数字的加法,因此我们也可以定义以下内容。

instance Num a => Monoid a where
    mappend = (+)
    mempty = 0

所以现在,在 GHCi 中,我们可以执行以下操作。

> mappend 3 5
8
> mempty
0

特别细心的读者(或具有数学背景的读者)现在可能已经注意到,我们还可以为 乘法 上的数字定义一个 Monoid 实例。

instance Num a => Monoid a where
    mappend = (*)
    mempty = 1

a * (b * c) == (a * b) * c
a * 1 == 1 * a == a

但是现在编译器遇到了问题。 mappend 的哪个定义应该用于数字? mappend 3 5 等于 8 还是 15?它没有办法决定。这就是为什么 Haskell 不允许单一类型的多个实例 class。但是,问题仍然存在。我们应该使用 Num 的哪个 Monoid 实例?两者都是完全有效的,并且在某些情况下有意义。解决方案是两者都不使用。如果你在 Hackage 中查看 Monoid,你会发现 NumIntegerIntFloat 没有 Monoid 个实例,或 Double 就此而言。相反,SumProductMonoid 个实例。 SumProduct定义如下。

newtype Sum a = Sum { getSum :: a }
newtype Product a = Product { getProduct :: a }

instance Num a => Monoid (Sum a) where
    mappend (Sum a) (Sum b) = Sum $ a + b
    mempty = Sum 0

instance Num a => Monoid (Product a) where
    mappend (Product a) (Product b) = Product $ a * b
    mempty = Product 1

现在,如果您想将数字用作 Monoid,则必须将其包装在 SumProduct 类型中。您使用的类型决定了使用哪个 Monoid 实例。这就是这篇文章试图描述的本质。 Haskell 的 typeclass 系统中没有内置允许您在多个实例之间进行选择的系统。相反,您必须通过在骨架类型中包装和展开它们来跳过箍。现在你是否认为这是一个问题是决定你更喜欢 Haskell 还是 ML 的很大一部分。

ML 通过允许在不同模块中定义相同 class 和类型的多个“实例”来解决这个问题。然后,您导入的模块决定了您使用的“实例”。 (严格来说,ML 没有 classes 和实例,但它确实有签名和结构,它们的行为几乎相同。如需更深入的比较,请阅读 this paper)。