Consider the Object-Oriented Languages:
大多数来自面向对象编程背景的人都熟悉各种语言中常见且直观的界面,这些界面捕获了Java的Collection&List接口的本质 . Collection 指的是不一定具有自然排序/索引的对象集合 . List 是一个具有自然排序/索引的集合 . 这些接口抽象了Java中的许多库数据结构,其他语言中的等效接口也是如此,并且需要对这些接口有深入的了解才能有效地与大多数库数据结构一起工作 .
Transition to Haskell:
Haskell有一个类型级系统,它类似于对象上的接口作用于类型 . 当类型考虑功能时,Haskell似乎有关于Functors,Applicative,Monads等的well designed type-class hierarchy . 他们显然想要correct and well-abstracted type-classes . 然而,当你看到许多Haskell的容器(List,Map,Sequence,Set,Vector)时,它们几乎都具有非常相似(或相同)的函数,但不是通过类型类抽象的 .
Some Examples:
-
null
用于测试"emptyness" -
length
/size
用于元素计数 -
elem
/member
用于设置包含 -
empty
和/或singleton
用于默认构造 -
union
for set union -
(\\)
/diff
用于设定差异 -
(!)
/(!!)
用于不安全索引(部分功能) -
(!?)
/lookup
用于安全索引(总功能)
如果我想使用上面的任何函数,但是我已经导入了两个或更多个容器,我必须从导入的模块中开始隐藏函数,或者只从模块中显式导入必要的函数,或者限定导入的模块 . 但由于所有功能都提供相同的逻辑功能,因此它似乎很麻烦 . 如果函数是从类型类定义的,而不是在每个模块中单独定义的,那么编译器的类型推理机制可以解决这个问题 . 只要它们共享类型类,它也会使底层容器切换变得简单(即:只使用 Sequence
而不是 List
以获得更好的随机访问效率) .
为什么Haskell没有 Collection 和/或 Indexable 类型类来统一和概括其中的一些函数?
7 回答
部分原因是单子和箭头是Haskell的新的创新功能,而收藏相对更平凡 . Haskell作为一种研究语言有着悠久的历史;有趣的研究问题(设计monad实例和定义monad的通用操作)比“工业强度”抛光(定义容器API)获得更多的开发工作 .
部分原因是这些类型来自三个不同的包(基础,容器和矢量),有三个独立的历史和设计师 . 这使得他们的设计师更难以协调提供任何单一类型的实例 .
部分原因是定义一个类型类来覆盖你提到的所有五个容器真的很难 . List,Sequence和Vector相对类似,但Map和Set具有完全不同的约束 . 对于List,Sequence和Vector,您需要一个简单的构造函数类,但对于不起作用的Set,因为Set需要元素类型上的Ord实例 . 更糟糕的是,Map可以支持你的大多数方法,但是它的单例函数需要两个参数,其余的只需要一个 .
lens包提供了一些 .
Testing for emptiness, creating empty containers 这些都是由Control.Lens.Empty的
AsEmpty
类型类提供的 .Accessing elements by key/index . 来自Control.Lens.At的
At
和Ixed
类型类 .Checking for membership in set-like containers . 来自Control.Lens.At的
Contains
类型类 .Appending and deleting elements to sequence-like containers . 来自Control.Lens.Cons的
Cons
和Snoc
类型类 .此外,
Applicative
类型类的pure
方法通常可用于创建"singleton"容器 . 对于Haskell中不是函子/应用程序的东西,如Set
,也许可以使用Data.Pointed来自Data.Pointed .正如其他答案所指出的,Haskell倾向于使用不同的词汇 . 但是,我没有很好地解释差异的原因 .
在像Java这样的语言中,功能不是“一等公民”;确实,匿名函数在最新版本中可用,但这种风格的界面(Collection,Indexable,Interable等)是在此之前设计的 .
这使得传递我们的代码变得冗长乏味,因此我们希望将其他人的数据传递给我们的代码 . 例如:
实现Java的数据
Iterable
让我们写for (Foo x : anIterable) { ... }
实现PHP的数据
ArrayAccess
让我们写anArrayAccess[anIndex]
这种风格也可以在实现生成器的OO语言中看到,因为这是我们的另一种方式写
for yieldedElement in aGenerator: ...
.Haskell对其类型类采用了不同的方法:我们更喜欢将代码传递给其他人的数据 . 一些(简化)示例:
Functor
接受我们的代码并将其应用于任何元素'contain'Monad
s接受我们的代码并将其应用于某种'sequence'Foldable
接受我们的代码并将其用于'reduce'其内容Java只需要
Iterable
因为我们必须在for
循环中调用我们的代码,所以我们可以确保它被正确调用 . Haskell需要更多特定的类型类,因为其他人的代码将调用我们的代码,因此我们需要指定它应该如何调用;是map
,fold
,unfold
等?值得庆幸的是,类型系统帮助我们选择正确的方法;)
Haskell有一些类型类用于处理基础包中的集合:Functor,Foldable和Traversable对于使用集合非常有用,Monoid,Applicative和/或Alternative类型类可用于构造集合 .
这些类一起涵盖了问题中提到的大多数操作,但可能效率低于更多容器特定的函数(尽管其中许多是类方法,如果需要,可以覆盖其默认定义) .
可折叠支持
null
,因为基础4.8(any (const True)
是早期版本的替代品) .可折叠支持
length
,因为基础4.8(getSum . foldMap (const 1)
是早期版本的替代品) .可折叠支持
elem
,_10447575_和member
.对于空,有来自Monoid的
mempty
和来自Alternative的empty
. 对于单身人士,来自Applicative的pure
.来自Monoid的
mappend
和来自Alternative的<|>
. 它们不一定实现set union,但它们实现了某种形式的联合,它与empty一起使用,通常也与singleton和find一起使用 .不幸的是,这个不受支持 .
您可以将
fromJust
与函数一起使用以进行安全索引 .Foldable有
find
.这种类型类存在于标准Haskell中,但它们与它们的等效OO对应物没有相同的名称 . 例如,
Collection
类型类在Haskell中称为Foldable
. 您可以使用它来测试结构是否为空(foldr (const False) True x
)或计算元素数(foldMap (const 1) x
),或者测试集合成员资格(某些e
为foldr (\e' present -> (e==e') || present) False x
) .对于元素查找等操作,您可以使用
Array
类型类,它可能适用于顺序数据 . 为了获得更大的灵活性,您可以编写自己的Indexable
类,例如(谨防镜头):null元素和set union属于
Monoid
类型类(其中mappend == union
) . 从这个角度来看,set差异也可以在它自己的类型类中实现Differentiable
(我肯定已经存在于几十个Haskell库中),我们将完全兼容命令式语言 .Haskell由于是由数学家等设计的,并没有像大多数其他语言那样使用相同的词汇,但是请放心,这并不意味着它不是一种实用的语言,除了是一个很棒的语言之外:-)
法律 . 一个好的类型类有法律 . 一个伟大的类型类具有足够的参数,因此它的定律是“免费的定理” . 没有法律的类型类只是临时名称重载 .
另外,请查看classy-prelude和Edison-API .
你有不同集合方面的类型类:
组成:Monoid(模块Data.Monoid)
顺序控制:Applicative,Monad(模块Control.Applicative,Control.Monad)
顺序组合:替代,MonadPlus(模块Control.Applicative,Control.Monad)
非顺序映射和缩减:Functor(mod.Data.Functor),Foldable(mod.Data.Foldable)
顺序映射和缩减:可遍历(模块Data.Traversable)
序列化:二进制(mod.Data.Binary)
比较:Eq,Ord(mod.Data.Eq,Data.Ord)
textualisation:显示,阅读
深度评估(到普通表格):NFData(mod.Sign.DeepSeq)
generic datatype traversability:Data(mod.Data.Data)
除了单态集合(ByteString,IntSet,Text)无法实现Functor和Foldable(它们需要类型arity == 1(种类:* - > *))
还neither (Set a) implements Functor .
包mono-traversable重新定义了一些没有单态类型排除的类 .
更新 . 尝试使用包mono-traversable和classy-prelude将大多数函数放在类型类中 .
library ref,platform