考虑面向对象的语言:
大多数来自面向对象编程背景的人都熟悉各种语言中常见且直观的界面,这些界面捕捉了Java Collection
& List
接口。 Collection
指的是一组对象,它们不一定具有自然的排序/索引。 List
是一个具有自然排序/索引的集合。这些接口在Java中抽象了许多库数据结构,其他语言中的等效接口也是如此,并且需要对这些接口有深入的了解才能有效地与大多数库数据结构协同工作。
过渡到Haskell:
Haskell有一个类型级系统,它类似于对象上的接口作用于类型。当类型考虑功能时,Haskell似乎有关于Functors,Applicative,Monads等的well designed type-class hierarchy。他们显然想要correct and well-abstracted type-classes。然而,当你看到许多Haskell的容器(List
,Map
,Sequence
,Set
,Vector
)时,它们几乎都具有非常相似(或相同)的功能,但不是通过类型类抽象的。
一些例子:
null
用于测试“空虚”length
/ size
elem
/ member
用于设置包含empty
和/或 singleton
union
for set union (\\)
/ diff
用于设置差异(!)
/ (!!)
用于不安全索引(部分功能)(!?)
/ lookup
安全索引(总功能)如果我想使用上述任何功能,但我已经导入了两个或更多个容器,我必须从导入的模块中开始隐藏功能,或者只从模块中显式导入必要的功能,或者对导入的模块进行限定。但由于所有功能都提供相同的逻辑功能,因此它似乎很麻烦。如果函数是从类型类定义的,而不是在每个模块中单独定义的,那么编译器的类型推理机制可以解决这个问题。只要它们共享类型类,它也可以简化切换底层容器(即:只使用Sequence
代替List
以获得更好的随机访问效率。)
为什么Haskell没有 Collection
和/或 Indexable
类型类来统一&概括了其中的一些功能?
答案 0 :(得分:38)
正如其他答案所指出的,Haskell倾向于使用不同的词汇。但是,我不认为他们已经很好地解释了原因的差异。
在像Java这样的语言中,功能不是“一等公民”;确实,匿名函数在最新版本中可用,但这种界面风格(Collection,Indexable,Interable等)是在此之前设计的。
这使我们传递代码变得冗长乏味,因此我们希望将其他人的数据传递给我们的代码。例如:
Iterable
的数据让我们写for (Foo x : anIterable) { ... }
ArrayAccess
的数据让我们写anArrayAccess[anIndex]
这种风格也可以在实现生成器的OO语言中看到,因为这是我们编写for yieldedElement in aGenerator: ...
的另一种方式。
Haskell对其类型类采用不同的方法:我们更喜欢将我们的代码传递给其他人的数据。一些(简化的)例子:
Functor
接受我们的代码并将其应用于他们包含的任何元素Monad
接受我们的代码并将其应用于某种“序列”Foldable
接受我们的代码并使用它来“减少”其内容 Java只需要Iterable
,因为我们必须在我们的 for
循环中调用我们的代码,因此我们可以确保它被正确调用。 Haskell需要更具体的类型类,因为其他人的代码将调用我们的代码,因此我们需要指定应该如何调用;它是map
,fold
,unfold
等吗?
值得庆幸的是,类型系统可以帮助我们选择正确的方法;)
答案 1 :(得分:36)
lens
包提供了部分内容。
测试空虚,创建空容器这些都是由Control.Lens.Empty
的AsEmpty
类型类提供的。
按键/索引访问元素。来自Control.Lens.At
的At
和Ixed
类型类。
检查类似集合容器的成员身份。来自Control.Lens.At
的Contains
类型类。
在类似序列的容器中追加和删除元素。来自Control.Lens.Cons
的Cons
和Snoc
类型类。
此外,pure
类型类的Applicative
方法通常可用于创建“单例”容器。对于Haskell中不是函子/应用程序的东西,例如Set
,可能会使用来自Data.Pointed
的point
。
答案 2 :(得分:25)
Haskell有一些类型类用于处理基础包中的集合:Functor,Foldable和Traversable可用于处理集合,Monoid, Applicative和/或Alternative类型类可用于构建集合。
这些类一起涵盖了问题中提到的大多数操作,但可能效率低于更多容器特定的函数(尽管其中许多是类方法,如果需要,可以覆盖其默认定义)。
null
用于测试"空虚"
可折叠支持null
,因为 base 4.8(any (const True)
是早期版本的替代品。)
元素数量的长度/大小:
可折叠支持length
,因为 base 4.8(getSum . foldMap (const 1)
是早期版本的替代品。)
elem /成员集合
可折叠支持elem
,notElem
和member
。
默认构造的空和/或单例
如果为空,则来自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库中),我们将完全兼容命令式语言。
答案 5 :(得分:12)
法。一个好的类型类有法律。一个伟大的类型类具有足够的参数,因此它的定律是免费的#34;定理。没有法律的类型类只是临时名称重载。
另外,请查看classy-prelude和Edison-API。
答案 6 :(得分:9)
您有不同收集方面的类型类:
组成: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)
文本化:显示,阅读
深度评估(至普通表格):NFData(mod.Control.DeepSeq)
泛型数据类型遍历:数据(mod.Data.Data)
除了单态集合(ByteString,IntSet,Text)无法实现Functor和Foldable(它们需要类型arity == 1(种类:* - &gt; *))
neither (Set a) implements Functor。
包mono-traversable重新定义了一些没有单态类型排除的类。
更新。尝试使用包mono-traversable和classy-prelude将大多数函数放在类型类中。