实例在Haskell中替代ZipList?

时间:2013-08-13 13:42:04

标签: list haskell typeclass applicative

ZipList附带一个Functor和一个Applicative实例(Control.Applicative),但为什么不选择?

  • 没有好的例子吗?
  • 下面提出的那个怎么样?
    • 有缺陷吗?
    • 没用吗?
    • 是否存在其他合理的可能性(例如Bool可以通过两种方式成为幺半群),因此 实例

我搜索了“实例替代ZipList”(引号首先找到代码)并且只找到了库,一些教程,讲义而没有实际的实例。

Matt Fenwick说,如果A是,则ZipList A只会是一个幺半群。 See here。无论元素类型如何,列表都是幺半群。

AndrewC对同一问题的

This other answer讨论了Alternative实例的外观。他说

  

Zip [1,3,4] <|> Zip [10,20,30,40]有两个合理的选择:

     
      
  1. Zip [1,3,4]因为它是第一个 - 与Maybe
  2. 一致   
  3. Zip [10,20,30,40]因为它最长 - 与Zip []被弃置一致
  4.   

其中Zip基本上是ZipList。 我认为答案应该是Zip [1,3,4,40]。我们来看一个实例:

instance Aternative Zip where
  empty = Zip []
  Zip xs <|> Zip ys = Zip (go xs ys) where
    go [] ys = ys
    go (x:xs) ys = x : go xs (drop 1 ys)

我们可以在不知道类型参数Zip a的情况下生成的唯一aZip [] :: Zip a,因此empty几乎没有选择。如果空列表是monoid的中性元素,我们可能会尝试使用列表连接。但是,由于go(++)不是drop 1。每次我们使用第一个参数列表中的一个条目时,我们会丢弃第二个参数列表中的一个。因此,我们有一种叠加:左边的参数列表隐藏了右边的一个(或全部)的开头。

[ 1, 3, 4,40]   [10,20,30,40]   [ 1, 3, 4]   [ 1, 3, 4]
  ^  ^  ^  ^      ^  ^  ^  ^      ^  ^  ^      ^  ^  ^
  |  |  |  |      |  |  |  |      |  |  |      |  |  |
[ 1, 3, 4] |    [10,20,30,40]   []|  |  |    [ 1, 3, 4]
[10,20,30,40]   [ 1, 3, 4]      [ 1, 3, 4]   []

拉链列表背后的一个直觉是过程:有限或无限的结果流。在压缩时,我们组合了流,这些流由Applicative实例反映出来。到达列表的末尾时,流不会产生更多元素。这是Alternative实例派上用场的地方:我们可以命名一个替换,一旦默认进程终止就接管。

例如,我们可以编写fmap Just foo <|> pure Nothing将ziplist foo的每个元素包装到Just中,然后继续Nothing。生成的ziplist是无限的,在所有(实际)值用完后恢复为默认值。这当然可以通过在Zip构造函数中附加无限列表来手动完成。然而,上述内容更优雅,并且不承担构造函数的知识,从而导致更高的代码可重用性。

我们不需要对元素类型进行任何假设(就像幺半群本身一样)。同时,定义并不简单(如(<|>) = const那样)。它通过第一个参数上的模式匹配来使用列表结构。

上面给出的<|>的定义是关联的,空列表实际上是空元素。我们有

Zip [] <*> xs = fs <*> Zip [] = Zip []
(fs <|> gs) <*> xs = fs <*> xs <|> gs <*> xs
fs <*> (xs <|> ys) = fs <*> xs <|> fs <*> ys

因此您可以要求的所有法律都得到满足(对于列表连接而言并非如此)。

此实例与Maybe的一致:选择偏向左侧,但当左参数无法生成值时,右侧参数将接管。功能

zipToMaybe :: Zip a -> Maybe a
zipToMaybe (Zip []) = Nothing
zipToMaybe (Zip (x:_)) = Just x

maybeToZip :: Maybe a -> Zip a
maybeToZip Nothing = Zip []
maybeToZip (Just x) = Zip (repeat x)

是替代品的态射(意为psi x <|> psi y = psi (x <|> y)psi x <*> psi y = psi (x <*> y))。

修改:对于我猜的some / many方法

some (Zip z) = Zip (map repeat z)
many (Zip z) = Zip (map repeat z ++ repeat [])

4 个答案:

答案 0 :(得分:3)

标签/不连续

有趣。一个并非完全不相关的想法:ZipLists可以被视为普通列表,其中元素由列表中的(增加的)位置索引标记。压缩应用程序通过配对同等索引的元素来连接两个列表。

想象一下列表中包含由(非递减)Ord 标记的元素的列表。 Zippery 应用程序将配对同等标记的元素,抛弃所有不匹配(it has its uses); zippery 替代可以对标记值执行保留左偏好联合的顺序(常规列表上的替代也是一种联合)。

这完全符合您对索引列表(又名ZipLists)的建议。

是的,这很有道理。

值列表的一种解释是非确定性,这与列表的monad实例一致,但ZipLists可以解释为按顺序组合的值的同步流。

使用此流解释时,您不会考虑整个列表,因此选择最长的流显然是作弊,并且在定义{{1}中正确解释从第一个ZipList到第二个的失败如你所说的那样,就像你在第一次完成时那样动态地做到这一点。

将两个列表压缩在一起不仅仅是因为类型签名,而是<|>的正确解释。

最长可能列表

将两个列表压缩在一起时,结果是两个长度中的最小值。这是因为这是在不使用⊥的情况下满足类型签名的最长列表。认为这是两个长度中较短的一个是错误的 - 这是最长的。

同样<|>应该生成最长的列表,它应该更喜欢左侧列表。显然它应该占据整个左侧列表并占据右侧列表,左侧离开以保持同步/ zippiness。

答案 1 :(得分:2)

你的实例没问题,但它确实是ZipList没有做的事情  (a)瞄准最长的名单,和  (b)在源列表之间混合元素。

在最短列表的长度停止操作。

这就是我在答案中总结的原因:

因此唯一合理的替代实例是:

instance Alternative Zip where
   empty = Zip []
   Zip [] <|> x = x
   Zip xs <|> _ = Zip xs

这与Maybe和解析器的Alternative实例一致,如果它没有失败,你应该a,如果它失败则跟b一起使用。你可以说较短的列表不如较长的列表成功,但我认为你不能说非空列表完全失败。

选择

empty = Zip []是因为它必须在列表的元素类型中是多态的,唯一的列表是[]

为了平衡,我认为你的实例并不可怕,我觉得这个更干净,但是嘿嘿,在你需要的时候自己滚动!

答案 2 :(得分:1)

我对Alternative的指导直觉来自解析器,它表明如果你的替代方案的一个分支以某种方式失败,它应该被根除,从而导致Longest - 样式Alternative可能不是'非常有用。这将是无偏见的(与解析器不同),但在无限列表上失败。

然后,他们所做的一切,正如你的建议,形成一个Monoid。尽管如此,你的ZipList通常不会体现你的偏见,你可以清楚地形成Alternative实例的反映版本。正如你所指出的那样,这也是Maybe的约定,但我不确定ZipList是否有任何理由遵循该惯例。

没有明智的somemany我不相信,虽然很少Alternative实际上有这些 - 也许他们被更好地隔离到{{1的子类中}}

坦率地说,我不认为你的建议是一个不好的实例,但我对它是Alternative隐含的“替代实例”没有任何信心。也许最好看看这种“扩展”实例可以应用于哪些地方(树?)并将其写为库。

答案 3 :(得分:0)

事实上,ZipList有一个明智的Alternative实例。它来自a paper on free near-semiringsMonadPlusAlternative是其中的示例):

instance Alternative ZipList where
  empty = ZipList []

  ZipList xs <|> ZipList ys = ZipList $ go xs ys where
    go [] bs = bs
    go as [] = as
    go (a:as) (_:bs) = a:go as bs

这是原始代码的更高性能版本,它是

ZipList xs <|> ZipList ys = ZipList $ xs ++ drop (length xs) ys