我最近在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?我应该注意哪些问题?
非常感谢你的帮助。
答案 0 :(得分:21)
一个例子:
Applicative对于解析很有用,因为你可以将部分的Applicative解析器转换为整体解析器,只需要一个纯函数来将这些部分组合成一个整体。
Divisible对于序列化非常有用(我们现在应该把这个版本调用吗?),因为你可以将部分的Divisible序列化器变成一个整体的序列化器,只需要一个纯函数就可以将整个部分分成几部分。
我实际上并没有看到过以这种方式工作的项目,但我(正在慢慢地)为Haskell开发Avro实现。
当我第一次遇到Divisible时,我想要divide
,并且不知道除了作弊之外可能有什么用conquer
(对于任何{f a
无处可寻{1}}?)。但是为了使我的序列化器a
能够检查出Divisible法则变成了一个“序列化器”,它将任何东西编码为零字节,这很有意义。
答案 1 :(得分:16)
这是一个可能的用例。
在流式库中,可以使用类似于foldl包中的类似折叠的结构,它们被输入一系列输入并在序列耗尽时返回汇总值。
这些折叠在其输入上是逆变的,可以Divisible
。这意味着如果你有一个元素流,其中每个元素可以以某种方式分解为b
和c
部分,并且你也碰巧有一个消耗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 a
和Group b
,那么我们可以Group (a, b)
(在一秒钟内更多内容)。 Henglein的核心思想是,如果你可以从一些基本的Group
开始整数 - 我们可以通过基数排序编写非常快的Group Int32
实现 - 然后使用组合器将它们扩展到所有类型,然后你将拥有广义基数排序到代数数据类型。
那么我们如何建立我们的组合子库呢?
嗯,f :: Group a -> Group b -> Group (a, b)
非常重要,因为它让我们可以制作类似产品的类型。通常情况下,我们会从Applicative
和liftA2
获得此信息,但您会注意到,Group
为Contravaiant
,而不是Functor
。
所以我们使用Divisible
divided :: Group a -> Group b -> Group (a, b)
请注意,这是从
以奇怪的方式产生的divide :: (a -> (b, c)) -> Group b -> Group c -> Group a
因为它具有逆变事物的典型“反向箭头”特征。现在,我们可以根据divide
对conquer
的解释来理解Group
和a
等内容。
除以表示如果我想制定一个策略来等同b
使用等于c
和x
的策略,我可以做关注任何类型[(a, x)]
获取您的部分关系f :: a -> (b, c)
,并使用函数[(b, (c, x))]
和一个小元组操作对其进行映射,以获得新的关系Group b
。
使用我的[(b, (c, x))]
将[[(c, x)]]
区分为Group c
使用我的[(c, x)]
区分每个[[x]]
到[[[x]]]
给我[[x]]
将内层展平以获得我们需要的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
我们也对更棘手的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向量操作来实现有效的基数排序。