Divisible Type类是否有用?

时间:2015-08-17 21:09:44

标签: haskell types functional-programming elm type-theory

我最近在Elm工作过一个API,其中一个主要类型是逆变。所以,我已经用Google搜索了解逆变类型可以做些什么,并发现the Contravariant package in Haskell defines the Divisible type class

定义如下:

class Contravariant f => Divisible f where
  divide  :: (a -> (b, c)) -> f b -> f c -> f a
  conquer :: f a 

事实证明,我的特定类型适合Divisible类型的定义。虽然Elm不支持类型类,但我会不时地查看Haskell以获得一些灵感。

我的问题:这种类型有任何实际用途吗? Haskell(或其他语言)中是否存在受益于这种分治模式的已知API?我应该注意哪些问题?

非常感谢你的帮助。

3 个答案:

答案 0 :(得分:21)

一个例子:

Applicative对于解析很有用,因为你可以将部分的Applicative解析器转换为整体解析器,只需要一个纯函数来将这些部分组合成一个整体。

Divisible对于序列化非常有用(我们现在应该把这个版本调用吗?),因为你可以将部分的Divisible序列化器变成一个整体的序列化器,只需要一个纯函数就可以将整个部分分成几部分。

我实际上并没有看到过以这种方式工作的项目,但我(正在慢慢地)为Haskell开发Avro实现。

当我第一次遇到Divisible时,我想要divide,并且不知道除了作弊之外可能有什么用conquer(对于任何{f a无处可寻{1}}?)。但是为了使我的序列化器a能够检查出Divisible法则变成了一个“序列化器”,它将任何东西编码为零字节,这很有意义。

答案 1 :(得分:16)

这是一个可能的用例。

在流式库中,可以使用类似于foldl包中的类似折叠的结构,它们被输入一系列输入并在序列耗尽时返回汇总值。

这些折叠在其输入上是逆变的,可以Divisible。这意味着如果你有一个元素流,其中每个元素可以以某种方式分解为bc部分,并且你也碰巧有一个消耗b和另一个折叠的折叠消耗c s,然后您可以构建一个消耗原始流的折叠。

来自foldl的实际弃号不会实现Divisible,但他们可以使用newtype包装器。在我的process-streaming包中,我有fold-like type确实实现了Divisible

divide要求组成折叠的返回值具有相同的类型,并且该类型必须是Monoid的实例。如果折叠返回不同的,不相关的幺半群,则解决方法是将每个返回值放在元组的单独字段中,将另一个字段保留为mempty。这是有效的,因为幺半群的元组本身就是Monoid

答案 2 :(得分:10)

我将研究由Edward Kmett在discrimination包中实现的Fritz Henglein的广义基数排序技术中的核心数据类型示例。

虽然那里有很多东西,但主要集中在这样的类型

data Group a = Group (forall b . [(a, b)] -> [[b]])

如果您的值为Group a类型,则a基本上必须具有等效关系,因为如果我在a和某些类型b之间为您提供关联你完全不知道,那么你可以给我b的“分组”。

groupId :: Group a -> [a] -> [[a]]
groupId (Group grouper) = grouper . map (\a -> (a, a))

您可以将此视为编写分组实用程序库的核心类型。例如,我们可能想要知道,如果我们可以Group aGroup b,那么我们可以Group (a, b)(在一秒钟内更多内容)。 Henglein的核心思想是,如果你可以从一些基本的Group开始整数 - 我们可以通过基数排序编写非常快的Group Int32实现 - 然后使用组合器将它们扩展到所有类型,然后你将拥有广义基数排序到代数数据类型。

那么我们如何建立我们的组合子库呢?

嗯,f :: Group a -> Group b -> Group (a, b)非常重要,因为它让我们可以制作类似产品的类型。通常情况下,我们会从ApplicativeliftA2获得此信息,但您会注意到,GroupContravaiant,而不是Functor

所以我们使用Divisible

divided :: Group a -> Group b -> Group (a, b)

请注意,这是从

以奇怪的方式产生的
divide :: (a -> (b, c)) -> Group b -> Group c -> Group a

因为它具有逆变事物的典型“反向箭头”特征。现在,我们可以根据divideconquer的解释来理解Groupa等内容。

除以表示如果我想制定一个策略来等同b使用等于cx的策略,我可以做关注任何类型[(a, x)]

  1. 获取您的部分关系f :: a -> (b, c),并使用函数[(b, (c, x))]和一个小元组操作对其进行映射,以获得新的关系Group b

    < / LI>
  2. 使用我的[(b, (c, x))][[(c, x)]]区分为Group c

  3. 使用我的[(c, x)]区分每个[[x]][[[x]]]给我[[x]]

  4. 将内层展平以获得我们需要的instance Divisible Group where conquer = Group $ return . fmap snd divide k (Group l) (Group r) = Group $ \xs -> -- a bit more cleverly done here... l [ (b, (c, d)) | (a,d) <- xs, let (b, c) = k a] >>= r

    class Divisible f => Decidable f where
      lose   :: (a -> Void) -> f a
      choose :: (a -> Either b c) -> f b -> f c -> f a
    
    instance Decidable Group where
      lose   :: (a -> Void) -> Group a
      choose :: (a -> Either b c) -> Group b -> Group c -> Group a
    
  5. 我们也对更棘手的Decidable refinement of Divisible

    进行了解释
    a

    这些内容如下所述:对于我们可以保证没有值的任何类型Void,我们无法通过任何方式生成a -> Void的值,函数Void是一种手段产生a给定a,因此我们不能以任何方式产生lose _ = Group (\_ -> []) 的值!)然后我们立即得到零值的分组

    divide

    我们也可以进行类似于上面Group的游戏,除了不对我们使用输入鉴别器进行排序,我们交替使用。

    使用这些技术,我们建立了一个“class Grouping a where grouping :: Group a 能够”的库,即Grouping

    groupingNat

    并注意来自X-CSRF-TOKEN顶部基本定义的nearly all the definitions arise,它使用快速monadic向量操作来实现有效的基数排序。