【问题标题】:(ML) Modules vs (Haskell) Type Classes(ML) 模块与 (Haskell) 类型类
【发布时间】:2016-08-23 22:56:10
【问题描述】:

根据 Harper (https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/) 的说法,类型类似乎根本无法提供与模块相同的抽象级别,我很难弄清楚原因。该链接中没有示例,因此我很难看到关键差异。还有其他关于如何在 Modules 和 Type Classes 之间进行转换的论文 (http://www.cse.unsw.edu.au/~chak/papers/modules-classes.pdf),但是从程序员的角度来看,这与实现没有任何关系(它只是说没有什么可以做的)其他人无法模仿)。

具体来说,在first link:

第一个是他们坚持一个类型可以以一种方式实现一个类型类。例如,根据类型类的哲学,整数可以精确地以一种方式排序(通常的排序),但显然有许多排序(例如,通过可分性)感兴趣。第二个是他们混淆了两个独立的问题:指定类型如何实现类型类以及指定在类型推断期间何时应使用此类规范。

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

第二个,这两者在 Haskell 中不是不同的吗? 指定“在类型推断期间何时应使用此类规范”可以通过以下方式完成:

blah :: BlahType b => ...

其中 BlahType 是在类型推断期间使用的类,而不是实现类。而“类型如何实现类型类”是使用 instance 完成的。

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

【问题讨论】:

标签: haskell sml ml


【解决方案1】:

要理解这篇文章的意思,请花点时间考虑一下 Haskell 中的 Monoid 类型类。幺半群是任何类型,T,它有一个函数 mappend :: T -> T -> T 和身份元素 empty :: 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 不允许单个类型类的多个实例的原因。但是,问题仍然存在。我们应该使用Num 的哪个Monoid 实例?两者都是完全有效的,并且在某些情况下是有意义的。解决方案是两者都不使用。如果你在 Hackage 中查看 Monoid,你会发现没有 Monoid 实例 Num、Integer、Int、Float 或 Double。相反,有Monoid 实例Sum 和Product。 Sum 和 Product 定义如下。

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,您必须将其包装在Sum 或Product 类型中。您使用哪种类型决定了使用哪个Monoid 实例。这就是这篇文章试图描述的本质。 Haskell 的类型类系统中没有内置系统允许您在多个实例之间进行选择。相反,您必须通过在骨架类型中包装和展开它们来跳过箍。现在,您是否认为这是一个问题,很大程度上决定了您更喜欢 Haskell 还是 ML。

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

【讨论】:

  • 机器学习如何解决这种歧义?是的,在 Haskell 中,您最终将不得不将其包装在另一种类型中。但是 ML 是如何处理它的呢? “一个类型可以在 ML 中以不止一种方式实现一个类型类?你如何在不创建新类型的情况下通过示例对整数进行排序?”并没有真正回答这个问题。
  • @RahulManne 根本不是 ML 程序员,但我相信它本质上允许您命名“实例”并具体选择要纳入范围的实例(通过使用 first - 用于此目的的模块而不是类型类)。
  • @Ben 如果是这种情况,我想知道,是否可以做类似的事情,在不同的文件中定义实例,并且导入该文件将只使用该实例?我试试看。
  • 虽然这对于Monoid 类可能是正确的,但是很容易创建一个多参数类型类,它有一个“tag”参数来指定你想要的实例。通常,您必须指定它,但这类似于使用限定的模块名称。在引用的论文中,编码使用关联类型族而不是多参数类型类,但想法是相同的。
  • 目前,Haskell 扩展 (OverlappingInstances) 中有一个错误功能,它顺便允许在某些情况下为一种类型指定更多实例,并根据具体情况选择哪个实例。不过,这被认为是一个错误。相反,在 Agda 中,类字典是隐式参数,如果需要,可以在每次调用时指定。因此,在 Agda 中,我们可以为同一类型拥有多个实例(代价是必须在每次调用时选择使用哪个实例)。 stackoverflow.com/questions/29504107/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多