为什么Haskell缺少"明显的"类型类

rec*_*nja 54 haskell abstraction standard-library typeclass

考虑面向对象的语言:

大多数人来自面向对象的编程背景,熟悉各种语言中常见且直观的界面,这些界面捕获了Java CollectionList界面的本质.Collection"对象"是指不一定具有自然排序/索引的对象集合.A List是具有自然排序/索引的集合.这些接口在Java中抽象了许多库数据结构,其他语言中的等效接口也是如此,并且需要对这些接口有深入的了解才能有效地与大多数库数据结构一起工作.

过渡到Haskell:

Haskell有一个类型级系统,它类似于对象上的接口作用于类型.当类型考虑功能时,Haskell似乎有一个关于Functors,Applicative,Monads等的设计良好的类型层次结构.他们显然想要正确且抽象良好的类型类.然而,当你看很多Haskell的容器(List,Map,Sequence,Set,Vector)他们几乎都具有非常相似(或相同)的功能,但通过类型类不是抽象的.

一些例子:

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

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

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

War*_*rbo 39

正如其他答案所指出的,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需要更多特定的类型类,因为其他人的代码将调用我们的代码,因此我们需要指定它应该如何调用; 是a map,a fold,an unfold等?

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


dan*_*iaz 36

lens软件包提供了部分内容.

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

  • 按键/索引访问元素.该AtIxed类型类的Control.Lens.At.

  • 检查类似集合的容器的成员资格.Contains来自的类型类Control.Lens.At.

  • 向类似序列的容器添加和删除元素.该ConsSnoc类型类的Control.Lens.Cons.

此外,类型类的pure方法Applicative通常可用于创建"单例"容器.对于事情是不是在Haskell仿函数/ applicatives一样Set,或许pointData.Pointed可使用.

  • 我真的认为很多镜头的类型类都是Haskell模块问题的解决方案,包括像这个例子中的"对接口编程". (4认同)

Tox*_*ris 26

Haskell有一些类型类用于处理基础包中的集合:Functor,FoldableTraversable可用于处理集合,而Monoid,Applicative和/或Alternative类型类可用于构建集合.

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

null 用于测试"空虚"

null自4.8 基础以来可折叠支持(any (const True)是早期版本的替代品).

元素数的长度/大小:

length自4.8 基础以来可折叠支持(getSum . foldMap (const 1)是早期版本的替代品).

elem /成员包含的成员

可折叠支撑elem,notElemmember.

默认构造的空和/或单例

对于空,有mempty来自Monoid和emptyAlternative.对于单身人士,有pure来自Applicative.

集合联盟

mappend来自含半幺群和<|>来自替代.它们不一定实现set union,但它们实现了某种形式的联合,它与empty一起使用,通常也与singleton和find一起使用.

(\)/ diff用于设置差异

不幸的是,这个不受支持.

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

您可以fromJust与功能一起使用以进行安全索引.

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

find由可折叠.

  • 来自`Foldable`的`elem`实际上是否使用容器的内部结构来加速检查?如果没有,使用它而不是容器特定的功能可能会损害性能. (4认同)
  • 我觉得,虽然功能正常,但这些建议并未正确捕获预期的抽象.许多例子中的*意图*都不容易扣除......来自[**`Foldable`**]的信息(http://hackage.haskell.org/package/base-4.7.0.1/docs/Data -Foldable.html)虽然很有趣! (2认同)
  • 你的`长度'的一个小改进:`getSum.foldMap(const 1)` (2认同)
  • 对于*base*(> = 4.8)的更新版本,`null`和`length`是`Foldable`的方法. (2认同)

Jon*_*ast 21

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

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

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

  • 我相信通用类型类的这些问题可以通过 `ConstraintKinds` 和 `TypeFamilies` 来解决 (2认同)

use*_*662 13

这种类型类存在于标准的Haskell中,但它们与它们的等效OO对应物没有相同的名称.该Collection类型类,例如,被称为Foldable在Haskell.您可以使用它来测试结构是否为空(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)
Run Code Online (Sandbox Code Playgroud)

null元素和set union属于Monoid类型类(where mappend == union).从这个角度Differentiable来看,set差异也可以在它自己的类型类中实现(我确信已经存在于几十个Haskell库中),并且我们将完全兼容命令式语言.

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


Boy*_*Jr. 12

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

另外,请查看classy-preludeEdison-API.


Gab*_*iba 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.Sign.DeepSeq)

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

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

没有(Set a)实现Functor.

遍历的包重新定义了一些类,没有单形类型排除.

更新.试图将大多数函数放在带有mono-traversableclassy-prelude的包的类型类中.

图书馆参考,平台