为什么Haskell缺少“明显的”类型类

时间:2014-08-07 20:32:20

标签: haskell typeclass abstraction standard-library

考虑面向对象的语言:

大多数来自面向对象编程背景的人都熟悉各种语言中常见且直观的界面,这些界面捕捉了Java Collection& List接口。 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 for set union
  • (\\) / diff用于设置差异
  • (!) / (!!)用于不安全索引(部分功能)
  • (!?) / lookup安全索引(总功能)

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

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

7 个答案:

答案 0 :(得分:38)

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

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

这使我们传递代码变得冗长乏味,因此我们希望将其他人的数据传递给我们的代码。例如:

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

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

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

  • Functor接受我们的代码并将其应用于他们包含的任何元素
  • Monad接受我们的代码并将其应用于某种“序列”
  • Foldable接受我们的代码并使用它来“减少”其内容

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

值得庆幸的是,类型系统可以帮助我们选择正确的方法;)

答案 1 :(得分:36)

lens包提供了部分内容。

  • 测试空虚,创建空容器这些都是由Control.Lens.EmptyAsEmpty类型类提供的。

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

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

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

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

答案 2 :(得分:25)

Haskell有一些类型类用于处理基础包中的集合:FunctorFoldableTraversable可用于处理集合,MonoidApplicative和/或Alternative类型类可用于构建集合。

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

  

null用于测试"空虚"

可折叠支持null,因为 base 4.8(any (const True)是早期版本的替代品。)

  

元素数量的长度/大小:

可折叠支持length,因为 base 4.8(getSum . foldMap (const 1)是早期版本的替代品。)

  

elem /成员集合

可折叠支持elemnotElemmember

  

默认构造的空和/或单例

如果为空,则来自Monoid的mempty和来自Alternative的empty。 对于单身人士,来自Applicative的pure

  

联合集合

来自Monoid的mappend和来自Alternative的<|>。他们没有必要实现集合联盟,但是他们实现了某种形式的联合,它与空的一起很好地工作,通常也与单身和查找一起工作。

  

(\)/ diff for set difference

不幸的是,不支持这个。

  

(!)/(!!)用于不安全索引(部分功能)

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

  

(!?)/查找安全索引(总函数)

Foldable有find

答案 3 :(得分:19)

部分原因是单子和箭头是Haskell的新功能,而收藏品相对更平凡。 Haskell作为一种研究语言有着悠久的历史;有趣的研究问题(设计monad实例和定义monad的通用操作)得到的开发工作量超过了工业强度&#34;抛光(定义容器API)。

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

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

答案 4 :(得分:13)

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

对于元素查找等操作,您可以使用e类型类,这可能适用于顺序数据。为了获得更大的灵活性,您可以编写自己的Array类,例如(谨防镜头):

Indexable

null元素和set union属于class Indexable m k a where at :: k -> Lens' m (Maybe a) 类型类(其中Monoid)。从这个角度来看,集合差异也可以在它自己的类型类mappend == union中实现(我确信已经存在于几十个Haskell库中),我们将完全兼容命令式语言。

Haskell由于是由数学家等设计的,并没有像大多数其他语言一样使用相同的词汇,但是请放心,它并不意味着它不是一个实用的词汇。语言除了令人敬畏之外: - )

答案 5 :(得分:12)

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

另外,请查看classy-preludeEdison-API

答案 6 :(得分:9)

您有不同收集方面的类型类:

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

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

  3. 顺序组合:替代,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)

  11. 除了单态集合(ByteString,IntSet,Text)无法实现Functor和Foldable(它们需要类型arity == 1(种类:* - &gt; *))

    neither (Set a) implements Functor

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

    更新。尝试使用包mono-traversableclassy-prelude将大多数函数放在类型类中。

    library refplatform