(机器学习)模块与(Haskell)类型类

28
根据Harper的说法(https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/),似乎类型类并没有提供与模块同等级别的抽象,但我很难确切地弄清楚为什么。该链接中也没有示例,因此很难看出关键区别。还有其他论文介绍如何在模块和类型类之间进行转换(http://www.cse.unsw.edu.au/~chak/papers/modules-classes.pdf),但这与程序员的实现无关(只是说其中一个不能模拟另一个)。具体来说,在first link中:
他们的第一个观点是类型只能以唯一的方式实现类别。例如,根据类型类的理念,整数可以按照精确的方法排序(通常排序),但显然有许多感兴趣的排序方法(例如,按可除性)。第二个观点是将两个分开的问题混淆在一起:指定类型如何实现类型类别和指定类型推断期间应使用此类别的说明时机。
blah :: BlahType b => ...

BlahType是类型推断过程中使用的类,而不是实现类。而“一个类型如何实现类型类”是通过instance实现的。

有人能解释一下这个链接到底想表达什么吗?我还不太明白为什么模块会比类型类更灵活。


这两个链接都是受限制的访问。老实说,这太奇怪了。 - Noein
1
对于第二个链接,https://web.archive.org/web/20180720210646/http://www.cse.unsw.edu.au/~chak/papers/modules-classes.pdf - bshanks
1个回答

25
为了理解文章的意思,请花一点时间考虑Haskell中的Monoid类型类。一个monoid是任何具有函数mappend :: T -> T -> T和恒等元素mempty :: T的类型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不允许单个typeclass的多个实例。然而,问题仍然存在。我们应该使用哪个Num的Monoid实例?两者都是完全有效的,并且在某些情况下是有意义的。解决方案是使用任何一个都不用。如果您在Hackage中查找Monoid,您会发现没有Num的Monoid实例,也没有Integer、Int、Float或Double的Monoid实例。相反,Sum和Product有Monoid实例,定义如下。
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的类型类系统中没有内置的系统允许您在多个实例之间进行选择。相反,您必须通过包装和解包它们来跳过骨架类型。现在,无论您是否认为这是一个问题,这在很大程度上决定了您喜欢Haskell还是ML。

ML通过允许在不同模块中定义相同类和类型的多个“实例”来解决这个问题。然后,导入哪个模块决定了您使用的“实例”。 (严格来说,ML没有类和实例,但它确实具有签名和结构,可以起到几乎相同的作用。有关更详细的比较,请阅读this paper。)


2
机器学习如何解决这种歧义?是的,在Haskell中,您最终将不得不将其包装在另一种类型中。但是ML如何处理它?“在ML中,一个类型可以以多种方式实现类型类吗?例如,如果没有创建新类型,您将如何按可除性对整数进行排序?”并没有真正回答这个问题。 - Rahul Manne
1
@RahulManne 并不是机器学习程序员,但我相信它基本上允许您命名“实例”,并具体选择要引入范围的实例(通过使用一等模块来实现此目的,而不是类型类)。 - Ben
3
对于该“Monoid”类而言这可能是正确的,但很容易创建一个具有“标记”参数的多参数类型类来指定所需的实例。通常需要手动指定,类似于使用限定模块名称。在被引用的论文中,编码使用关联类型族而非多参数类型类,但思路相同。 - Derek Elkins left SE
5
目前,在 Haskell 的某个扩展中(OverlappingInstances),存在一个缺陷,可以在某些情况下为类型指定更多的实例,并且可以根据每种情况选择使用哪种实例。尽管这被认为是一个 bug。相比之下,Agda 中的类字典是隐式参数,如果需要,可以在每次调用时指定。因此,在 Agda 中,我们可以为同一类型有多个实例(代价是必须在每次调用时选择使用哪个实例)。https://dev59.com/wF0b5IYBdhLWcg3wLOrP - chi
除了编码类型类之外,ML模块还可以做很多其他事情,对吧?我认为它们是一个比Haskell类型类更通用的概念。 - Lii
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接