首页 文章

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

提问于
浏览
51

Consider the Object-Oriented Languages:

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

Transition to Haskell:

Haskell有一个类型级系统,它类似于对象上的接口作用于类型 . 当类型考虑功能时,Haskell似乎有关于Functors,Applicative,Monads等的well designed type-class hierarchy . 他们显然想要correct and well-abstracted type-classes . 然而,当你看到许多Haskell的容器(ListMapSequenceSetVector)时,它们几乎都具有非常相似(或相同)的函数,但不是通过类型类抽象的 .

Some Examples:

  • null 用于测试"emptyness"

  • length / size 用于元素计数

  • elem / member 用于设置包含

  • empty 和/或 singleton 用于默认构造

  • union for set union

  • (\\) / diff 用于设定差异

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

  • (!?) / lookup 用于安全索引(总功能)

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

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

7 回答

  • 9

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

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

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

  • 35

    lens包提供了一些 .

    • Testing for emptiness, creating empty containers 这些都是由Control.Lens.EmptyAsEmpty 类型类提供的 .

    • Accessing elements by key/index . 来自Control.Lens.AtAtIxed 类型类 .

    • Checking for membership in set-like containers . 来自Control.Lens.AtContains 类型类 .

    • Appending and deleting elements to sequence-like containers . 来自Control.Lens.ConsConsSnoc 类型类 .

    此外, Applicative 类型类的 pure 方法通常可用于创建"singleton"容器 . 对于Haskell中不是函子/应用程序的东西,如 Set ,也许可以使用Data.Pointed来自Data.Pointed .

  • 17

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

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

  • 11

    Haskell有一些类型类用于处理基础包中的集合:FunctorFoldableTraversable对于使用集合非常有用,MonoidApplicative和/或Alternative类型类可用于构造集合 .

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

    null用于测试“空白”

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

    元素数量的长度/大小:

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

    elem /成员集合

    可折叠支持 elem ,_10447575_和 member .

    默认构造的空和/或单例

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

    集合联盟

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

    (\)/ diff用于设置差异

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

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

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

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

    Foldable有 find .

  • 25

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

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

    class Indexable m k a where
      at :: k -> Lens' m (Maybe a)
    

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

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

  • 13

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

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

  • 35

    你有不同集合方面的类型类:

    • 组成: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-traversableclassy-prelude将大多数函数放在类型类中 .

    library refplatform

相关问题