为什么Haskell错过了“显而易见的”types
考虑面向对象的语言:
大多数来自面向对象编程背景的人都熟悉各种语言中常见的直观界面,这些界面捕捉了Java Collection
& List
界面的精髓。 Collection
是指不一定具有自然顺序/索引的对象的集合。 List
是一个具有自然顺序/索引的集合。 这些接口抽象了Java中的许多库数据结构,就像他们在其他语言中的等价接口一样,并且需要对这些接口有一个深入的了解才能与大多数库数据结构有效地协作。
过渡到Haskell:
Haskell有一个types类系统,类似于对象上的接口types。 Haskell似乎有一个关于Functors,Applicative,Monads等devise良好的types层次结构 。 他们显然需要正确和抽象的types 。 然而,当你看到许多Haskell的容器( List
, Map
, Sequence
, Set
, Vector
)时,它们几乎都具有非常相似的(或相同的)function,但不是通过types类来抽象的。
一些例子:
-
null
用于testing“null
” - 元素数量的
length
/size
-
elem
/member
列入集合 -
empty
和/或singleton
默认构造 -
union
联盟 -
(\\)
/diff
设置差异 -
(!)
/(!!)
进行不安全的索引(部分function) -
(!?)
/lookup
安全索引(总function)
如果我想使用上面的任何函数,但是我导入了两个或多个容器,我必须从导入的模块中隐藏函数,或者只从模块导入必要的函数,或者限定导入的模块。 但是因为所有的function都提供了相同的逻辑function,所以看起来就像一个麻烦。 如果函数是从types类定义的,而不是在每个模块中单独定义的,那么编译器的types推断机制就可以解决这个问题。 只要他们共享types类(例如:让我们只使用Sequence
而不是List
来获得更好的随机访问效率),它也会使底层容器的切换变得简单。
为什么Haskell没有一个Collection
和/或Indexable
types的类来统一和概括这些函数呢?
部分原因是单向箭头是Haskell新的创新function,而集合相对来说比较平凡。 Haskell作为一门研究语言有着悠久的历史, 有趣的研究问题(devisemonad实例和定义monads的通用操作)比“工业强度”抛光(定义容器API)需要更多的开发工作。
部分原因是这些types来自三个不同的包(基础,容器和vector),有三个独立的历史和devise师。 这使得他们的devise师难以协调提供任何单一types的实例。
部分原因是定义一个types类来覆盖你提到的所有五个容器是非常困难的。 List,Sequence和Vector是比较相似的,但是Map和Set有完全不同的约束。 对于List,Sequence和Vector,你需要一个简单的构造函数类,但是对于Set来说,这是行不通的,因为Set需要元素types的Ord实例。 更糟糕的是,Map可以支持大多数的方法,但是它的单例函数需要两个参数,其余的只需要一个。
lens
包提供了一些这一点。
-
testing空,创build空容器这些都是由
Control.Lens.Empty
的AsEmpty
types类提供的。 -
通过键/索引访问元素 。 来自
Control.Lens.At
的At
和Ixed
types类。 -
检查集合类容器中的成员资格 。
Contains
来自Control.Lens.At
types类。 -
追加和删除元素到类似序列的容器 。
Control.Lens.Cons
的Cons
和Snoc
types类。
此外, Applicative
types的pure
方法通常可以用来创build“单件”容器。 对于Haskell中不是函子/应用程序的东西,比如Set
,也许从Data.Pointed
point
可以使用。
正如其他答案指出的,Haskell倾向于使用不同的词汇。 但是,我不认为他们已经很好地解释了差异的原因 。
在像Java这样的语言中,函数不是“一等公民”。 在最新版本中,匿名函数是可用的,但是这种types的接口(Collection,Indexable,Interable等)是在此之前devise的。
这使得我们的代码周转乏味,所以我们更喜欢将其他人的数据传递给我们的代码 。 例如:
- 实现Java的
Iterable
数据让我们写for (Foo x : anIterable) { ... }
- 实现PHP
ArrayAccess
数据让我们编写一个anArrayAccess[anIndex]
这种风格也可以在实现生成器的OO语言中看到,因为这是我们在生成器中for yieldedElement in aGenerator: ...
写入的另一种方式for yieldedElement in aGenerator: ...
Haskell采用不同的方法来处理types:我们希望我们的代码被传递给其他人的数据 。 一些(简化的)例子:
-
Functor
s接受我们的代码,并将其应用于“包含” -
Monad
接受我们的代码并将其应用于某种“序列” -
Foldable
s接受我们的代码,并用它来“减less”他们的内容
Java只需要Iterable
因为我们必须在我们的 for
循环中调用我们的代码,所以我们可以确保它的调用是正确的。 Haskell需要更具体的types类,因为别人的代码会调用我们的,所以我们需要指定它应该如何调用; 是map
, fold
unfold
等等?
谢天谢地,types系统帮助我们select正确的方法;)
Haskell有一些用于处理基本包中的集合的types类: Functor , Foldable和Traversable可以用于处理集合,而Monoid , Applicative和/或者Alternativetypestypes可以用于构build集合。
这些类共同涵盖了问题中提到的大多数操作,但是可能比更多的容器特定的函数(尽pipe其中许多是类方法,其默认定义可以在必要时被重写)效率更低。
null
用于testing“null
”
可折叠支持null
因为基数为 4.8( any (const True)
是早期版本的替代)。
元素数量的长度/大小:
可折叠的支持length
自4.8( getSum . foldMap (const 1)
是早期版本的替代)。
elem /成员列入集合
可折叠支持elem
,而notElem
和member
。
空的和/或单身默认构造
对于空的,来自Monoid的mempty
是空的,并且来自Alternative。 对于单身人士来说,从申请人是pure
。
工会联盟
有来自Monoid的mappend
和来自Alternative的<|>
。 他们不一定要实现组合,而是实现某种forms的组合,这些组合通常与空单元一起工作,通常也与单单单元一起工作并find。
(\)/差异设置差异
不幸的是这个不支持。
(!)/(!!)进行不安全的索引(部分function)
你可以使用fromJust
和一个用于安全索引的函数。
(!?)/查找安全索引(总function)
有从Foldable find
。
这样的types类存在于标准的Haskell中,但是它们不具有与其等价的OO相同的名称。 Collection
types类例如在Haskell中被称为Foldable
。 您可以使用它来testing一个结构是否为空( foldr (const False) True x
)或计算元素数目( foldMap (const 1) x
),或者testing集合成员资格( foldr (\e' present -> (e==e') || present) False x
foldMap (const 1) x
foldr (\e' present -> (e==e') || present) False x
对于某些e
foldr (\e' present -> (e==e') || present) False x
。
对于元素查找等操作,您可以使用可能适用于顺序数据的Array
typestypes。 为了获得更多的灵活性,你可以编写自己的Indexable
类,例如(注意镜头):
class Indexable mka where at :: k -> Lens' m (Maybe a)
null元素和set union属于Monoid
typestypes(其中mappend == union
)。 有鉴于此,集合差异也可以在自己的types类Differentiable
(我相信在几十个Haskell库中已经存在这个类),我们将与命令式语言完全兼容。
Haskell由于由math家等devise,并没有采用与大多数其他语言相同的词汇,但是放心,这并不意味着它不是一个实用的语言,除了是一个真棒语言:-)
法律。 一个好的types有法律。 一个伟大的types具有足够的参数,所以它的规律是“自由的定理”。 没有法律的typestypes只是特定的名称重载。
此外,请检查优雅的前奏和爱迪生API 。
你有不同的集合方面的types:
-
组成:Monoid(模块Data.Monoid)
-
顺序控制:适用,Monad(模块Control.Applicative,Control.Monad)
-
顺序组成:另类,MonadPlus(模块Control.Applicative,Control.Monad)
-
非顺序映射和缩减:Functor(mod.Data.Functor),Foldable(mod.Data.Foldable)
-
顺序映射和缩减:Traversable(模块Data.Traversable)
-
序列化:二进制(mod。Data.Binary)
-
比较:Eq,Ord(mod。Data.Eq,Data.Ord)
-
文本化:显示,阅读
-
深度评估(以正常forms):NFData(mod。Control.DeepSeq)
-
通用数据types可遍历性:Data(mod。Data.Data)
除了单形态集合(ByteString,IntSet,Text)不能实现Functor和Foldable(它们需要typesarity == 1(Kind:* – > *))
也没有(设置)实现Functor 。
单遍的包重新定义了一些没有单态types排除的类。
更新 。 有一种尝试把大部分函数都放在types类中,并且使用包可穿越和优雅的前奏 。
库参考 , 平台