12

ZipList comes with a Functor and an Applicative instance (Control.Applicative) but why not Alternative?

  • Is there no good instance?
  • What about the one proposed below?
    • Is it flawed?
    • is it useless?
    • Are there other reasonable possibilities (like Bool can be a monoid in two ways) and therefore neither should be the instance?

I searched for "instance Alternative ZipList" (with the quotes to find code first) and only found the library, some tutorials, lecture notes yet no actual instance.

Matt Fenwick said ZipList A will only be a monoid if A is (see here). Lists are monoids though, regardless of the element type.

This other answer by AndrewC to the same question discusses how an Alternative instance might look like. He says

There are two sensible choices for Zip [1,3,4] <|> Zip [10,20,30,40]:

  1. Zip [1,3,4] because it's first - consistent with Maybe
  2. Zip [10,20,30,40] because it's longest - consistent with Zip [] being discarded

where Zip is basically ZipList.

I think the answer should be Zip [1,3,4,40]. Let's see the instance:

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)

The only Zip a we can produce without knowing the type argument a is Zip [] :: Zip a, so there is little choice for empty. If the empty list is the neutral element of the monoid, we might be tempted to use list concatenation. However, go is not (++) because of the drop 1. Every time we use one entry of the first argument list, we drop one off the second as well. Thus we have a kind of overlay: The left argument list hides the beginning of the right one (or all of it).

[ 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]   []

One intuition behind ziplists is processes: A finite or infinite stream of results. When zipping, we combine streams, which is reflected by the Applicative instance. When the end of the list is reached, the stream doesn't produce further elements. This is where the Alternative instance comes in handy: we can name a concurrent replacement (alternative, really), taking over as soon as the default process terminates.

For example we could write fmap Just foo <|> pure Nothing to wrap every element of the ziplist foo into a Just and continue with Nothing afterwards. The resulting ziplist is infinite, reverting to a default value after all (real) values have been used up. This could of course be done by hand, by appending an infinite list inside the Zip constructor. Yet the above is more elegant and does not assume knowledge of constructors, leading to higher code reusability.

We don't need any assumption on the element type (like being a monoid itself). At the same time the definition is not trivial (as (<|>) = const would be). It makes use of the list structure by pattern matching on the first argument.

The definition of <|> given above is associative and the empty list really is the empty element. We have

Zip [] <*> xs  ==  fs <*> Zip []  ==  Zip []     -- 0*x = x*0 = 0
Zip [] <|> xs  ==  xs <|> Zip []  ==  xs         -- 0+x = x+0 = x
(fs <|> gs) <*> xs  ==  fs <*> xs <|> gs <*> xs
 fs <*> (xs <|> ys) ==  fs <*> xs <|> fs <*> ys

so all the laws you could ask for are satisfied (which is not true for list concatenation).

This instance is consistent with the one for Maybe: choice is biased to the left, yet when the left argument is unable to produce a value, the right argument takes over. The functions

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)

are morphisms of alternatives (meaning psi x <|> psi y = psi (x <|> y) and psi x <*> psi y = psi (x <*> y)).

Edit: For the some/many methods I'd guess

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

4 回答 4

5

标签/指标

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

想象一下带有由(非递减)Ord 标记的元素的列表。Zippery应用程序将配对相同标记的元素,丢弃所有不匹配(它有它的用途);zippery替代方案可以对标签值执行保持顺序的左优先联合(常规列表上的替代方案也是一种联合)。

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

所以是的,这是有道理的。

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

使用这种流解释,您不会考虑整个列表,因此选择最长的流显然是作弊,并且从定义中的第一个 ZipList 故障转移到第二个的正确解释是<|>在正如您在您的实例中所说,作为第一个完成飞行。

将两个列表压缩在一起并不仅仅因为类型签名而这样做,但它是对<|>.

最长的可能列表

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

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

于 2013-08-13T16:42:50.603 回答
2

您的实例没问题,但它做了一些 ZipList 没有做到的事情
(a)针对最长的列表,以及
(b)在源列表之间混合元素。

压缩作为操作在最短列表的长度处停止。

这就是为什么我在回答中得出结论:

因此,唯一合理的 Alternative 实例是:

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

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

empty = Zip []之所以选择它,是因为它在列表的元素类型中必须是多态的,并且唯一这样的列表是[]

为了平衡,我不认为你的实例很糟糕,我认为这更干净,但是嘿嘿,你需要的时候自己动手吧!

于 2013-08-13T22:10:10.243 回答
2

事实上AlternativeZipList 有一个合理的实例。它来自一篇关于免费近半环的论文MonadPlus并且Alternative是示例):

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
于 2017-10-21T18:40:55.713 回答
1

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

再说一次,正如您所建议的,他们必须做的就是形成一个Monoid. 但是,您的偏向ZipList通常不会体现出来 - 您可以很Alternative容易地清楚地形成您的实例的反映版本。正如您所指出的,这也是惯例Maybe,但我不确定是否有任何理由ZipList遵循该惯例。

没有明智的some,或者many我不相信,虽然很少Alternatives 实际上有这些——也许他们最好被隔离到Alternative.

坦率地说,我不认为你的建议是一个糟糕的例子,但我对它是拥有ZipList. 也许最好看看这种“扩展”实例还能在哪里应用(树?)并将其编写为库。

于 2013-08-13T15:24:01.957 回答