【问题标题】:Why is Haskell missing "obvious" Typeclasses为什么 Haskell 缺少“明显的”类型类
【发布时间】:2014-10-01 05:37:49
【问题描述】:

考虑面向对象的语言:

大多数具有面向对象编程背景的人都熟悉各种语言中常见且直观的接口,这些接口抓住了 Java 的CollectionList 接口的精髓。 Collection 指的是不一定具有自然排序/索引的对象集合。 List 是一个具有自然排序/索引的集合。这些接口抽象了 Java 中的许多库数据结构,与其他语言中的等效接口一样,需要深入了解这些接口才能有效地处理大多数库数据结构。

过渡到 Haskell:

Haskell 有一个类型类系统,它作用于类型类似于对象上的接口。当类型涉及功能时,Haskell 似乎有一个关于 Functors、Applicative、Monads 等的well designed type-class hierarchy。他们显然想要correct and well-abstracted type-classes。然而,当您查看许多 Haskell 的容器(ListMapSequenceSetVector)时,它们几乎都具有非常相似(或相同)的功能,但并未通过类型类进行抽象.

一些例子:

  • null 用于测试“空性”
  • length/size 用于元素计数
  • elem/member 用于集合包含
  • empty 和/或 singleton 默认构造
  • union 用于设置联合
  • (\\)/diff 设置差异
  • (!)/(!!) 用于不安全的索引(部分功能)
  • (!?)/lookup 用于安全索引(全功能)

如果我想使用上述任何函数,但我已经导入了两个或更多容器,我必须开始从导入的模块中隐藏函数,或者仅从模块中显式导入必要的函数,或者限定导入的模块。但由于所有功能都提供相同的逻辑功能,所以看起来很麻烦。如果函数是从类型类定义的,而不是在每个模块中单独定义,编译器的类型推断机制可以解决这个问题。只要它们共享类型类,它还将使切换底层容器变得简单(即:让我们只使用Sequence 而不是List 以获得更好的随机访问效率)。

为什么 Haskell 没有一个 Collection 和/或 Indexable 类型类来统一和概括其中的一些功能? p>

【问题讨论】:

  • 简要评论我的近距离投票:因为显然 are 库提供了相关的类型类,在我看来,解释这个问题的最慈善的方式可能是“为什么人们不是在使用这些类型类吗?”。我认为很难以客观、有用的方式回答这个问题。
  • 一些心灵的食物:图书馆将如何处理额外的限制?比较 isMember :: Ord k => k -> Set k -> BoolisMember :: a -> [a] -> Bool。或索引:at :: Int -> [a] -> Maybe a vs at :: Unbox a => Int -> Vector a -> Maybe a(用于未装箱的向量)。除此之外,我同意丹尼尔的观点,很难以客观的方式回答。如果您可以创建您的特定版本的Collection,那就去做吧,并将其添加到 hackage。
  • @awashburn: { ... Zeta 评论的副本,已删除 ... } 也就是说,ConstraintKinds 实际上可以实现高效的通用elem

标签: haskell typeclass abstraction standard-library


【解决方案1】:

Haskell 在基础包中有一些用于处理集合的类型类:FunctorFoldableTraversable 可用于处理集合,MonoidApplicative 和/或@987654326 @typeclasses 对于构造集合很有用。

这些类共同涵盖了问题中提到的大部分操作,但效率可能低于更特定于容器的函数(尽管其中许多是类方法,如果需要,可以覆盖其默认定义)。

null 用于测试“空性”

base 4.8 起可折叠支持nullany (const True) 是早期版本的替代品)。

元素计数的长度/大小:

base 4.8 起可折叠支持lengthgetSum . foldMap (const 1) 是早期版本的替代品)。

用于集合包含的元素/成员

可折叠支持elemnotElemmember

默认构造为空和/或单例

对于空,有来自 Monoid 的 mempty 和来自 Alternative 的 empty。 对于单例,有来自 Applicative 的pure

集合并集的并集

有来自 Monoid 的 mappend 和来自 Alternative 的 <|>。它们不一定实现集合并集,但它们实现了某种形式的并集,可以很好地与 empty 一起使用,通常也可以与单例和 find 一起使用。

(\)/diff 设置差异

很遗憾,不支持这个。

(!)/(!!) 用于不安全的索引(部分函数)

您可以将fromJust 与用于安全索引的函数一起使用。

(!?)/查找安全索引(全功能)

有来自 Foldable 的find

【讨论】:

  • elem from Foldable 是否真的使用容器的内部结构来加速检查?如果没有,使用它而不是容器特定的函数可能会损害性能。
  • 我觉得这些建议虽然实用,但并没有正确地捕捉到预期的抽象。您的许多示例中的 意图 并不容易扣除......不过来自Foldable 的信息很有趣!
  • 您可以将Foldable 看作是一种奇特的说法,即有一个函数Foldable f => f a -> [a]---所以所有的实例化器都从List 继承了很多功能。
  • 对您的length 的小改进:getSum . foldMap (const 1)
  • 对于较新版本的 base (>= 4.8),nulllengthFoldable 的方法。
【解决方案2】:

你有不同集合方面的类型类:

  1. 组成:Monoid(模块 Data.Monoid)

  2. 顺序控制:Applicative、Monad(模块 Control.Applicative、Control.Monad)

  3. 顺序组合:Alternative、MonadPlus(模块 Control.Applicative、Control.Monad)

  4. 非顺序映射和归约:Functor (mod.Data.Functor)、Foldable (mod.Data.Foldable)

  5. 顺序映射和归约:可遍历(模块 Data.Traversable)

  6. 序列化:二进制(mod.Data.Binary)

  7. 比较:Eq、Ord(mod.Data.Eq、Data.Ord)

  8. 文本化:显示、阅读

  9. 深度评估(到范式):NFData(mod.Control.DeepSeq)

  10. 通用数据类型可遍历性:数据(mod.Data.Data)

除了单态集合(ByteString、IntSet、Text)不能实现 Functor 和 Foldable(它们需要 type arity == 1 (Kind: * -> *))

还有neither (Set a) implements Functor

mono-traversable 重新定义了一些没有排除单态类型的类。

更新。尝试将大多数函数放入带有包 mono-traversableclassy-prelude 的类型类中。

library refplatform

【讨论】:

    【解决方案3】:

    此类类型类存在于标准 Haskell 中,但它们的名称与其等效的 OO 对应物的名称不同。例如,Collection 类型类在 Haskell 中称为Foldable。您可以使用它来测试结构是否为空 (foldr (const False) True x) 或计算元素的数量 (foldMap (const 1) x),或测试集合成员资格(foldr (\e' present -> (e==e') || present) False x 用于某些 e)。

    对于像元素查找这样的操作,您有Array 类型类,它可能适用于顺序数据。为了获得更大的灵活性,您可以编写自己的 Indexable 类,例如(注意镜头):

    class Indexable m k a where
      at :: k -> Lens' m (Maybe a)
    

    null 元素和集合并集属于Monoid 类型类(其中mappend == union)。有鉴于此,集合差异也可以在它自己的类型类 Differentiable 中实现(我确信它已经存在于几十个 Haskell 库中)并且我们将与命令式语言完全兼容。

    Haskell,由于是由数学家之类的人设计的,它没有使用与大多数其他语言相同的词汇,但请放心,这并不意味着它不是一门实用的语言,而是一种很棒的语言:-)

    【讨论】:

      【解决方案4】:

      正如其他答案所指出的,Haskell 倾向于使用不同的词汇。但是,我认为他们没有很好地解释差异的原因

      在像 Java 这样的语言中,函数不是“一等公民”;匿名函数确实在最新版本中可用,但这种接口风格(Collection、Indexable、Interable 等)是在那之前设计的。

      这使得传递我们的代码变得乏味,因此我们更喜欢将其他人的数据传递给我们的代码。例如:

      • 实现 Java 的 Iterable 的数据让 我们 写入 for (Foo x : anIterable) { ... }
      • 实现 PHP 的 ArrayAccess 的数据让 我们 写入 anArrayAccess[anIndex]

      这种风格也可以在实现生成器的 OO 语言中看到,因为这是 us 编写 for yieldedElement in aGenerator: ... 的另一种方式。

      Haskell 对其类型类采取了不同的方法:我们更喜欢将我们的代码传递给其他人的数据。一些(简化的)示例:

      • Functors 接受我们的代码并将其应用于它们“包含”的任何元素
      • Monads 接受我们的代码并将其应用到某种“序列”中
      • Foldables 接受我们的代码并用它来“减少”他们的内容

      Java 只需要Iterable,因为我们 必须在我们的 for 循环中调用我们的代码,因此我们可以确保它被正确调用。 Haskell 需要更具体的类型类,因为其他人的代码会调用我们的,所以我们需要指定如何调用它;是mapfoldunfold 等吗?

      谢天谢地,类型系统帮助我们选择正确的方法;)

      【讨论】:

      • 很酷的答案 - 我相信它给出了缺乏“数据接口”类型类的最根本原因。但它也邀请我们以“另一种方式”看待数据访问问题。
      【解决方案5】:

      法律。一个好的类型类有规律。一个伟大的类型类具有足够的参数性,因此它的定律是“免费定理”。没有法律的类型类只是临时名称重载。

      另外,请查看classy-preludeEdison-API

      【讨论】:

        【解决方案6】:

        lens 包提供了其中的一部分。

        • 测试空性,创建空容器这些都由来自Control.Lens.EmptyAsEmpty 类型类提供。

        • 通过键/索引访问元素。来自Control.Lens.AtAtIxed 类型类。

        • 检查类似集合的容器中的成员身份。来自Control.Lens.AtContains 类型类。

        • 向类似序列的容器添加和删除元素。来自Control.Lens.ConsConsSnoc 类型类。

        此外,Applicative 类型类的pure 方法通常可用于创建“单例”容器。对于 Haskell 中不是函子/应用程序的东西,例如 Set,也许可以使用来自 Data.Pointedpoint

        【讨论】:

        • 我真的认为很多 lens 的 typeclasses 是 Haskell 模块问题的解决方案,包括像这个例子中的“针对接口编程”。
        【解决方案7】:

        部分原因是 monads 和 arrows 是 Haskell 的新的、创新的特性,而集合相对来说比较普通。 Haskell 作为一种研究语言有着悠久的历史。有趣的研究问题(设计 monad 实例和定义 monad 的通用操作)比“工业强度”抛光(定义容器 API)需要更多的开发工作。

        部分原因是这些类型来自三个不同的包(基础、容器和矢量),具有三个独立的历史和设计器。这使得他们的设计人员更难协调提供任何单一类型类的实例。

        部分原因是定义一个类型类来涵盖您提到的所有五个容器确实很难。 List、Sequence 和 Vector 比较相似,但 Map 和 Set 具有完全不同的约束条件。对于 List、Sequence 和 Vector,您需要一个简单的构造函数类,但对于 Set,它不起作用,因为 Set 需要元素类型上的 Ord 实例。更糟糕的是,Map 可以支持你的大部分方法,但是它的单例函数需要两个参数,而其余的只需要一个。

        【讨论】:

        • 有趣的是,我从来没有想过用 Haskell 的学术偏差与行业使用来解释......
        • 我相信这些通用类型类的问题可以通过ConstraintKindsTypeFamilies来解决
        • 经过深思熟虑,我觉得您的回答最符合我的问题的精神。
        猜你喜欢
        • 1970-01-01
        • 2011-09-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-02
        • 1970-01-01
        • 2016-04-16
        • 2022-08-12
        相关资源
        最近更新 更多