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]
有两个合理的选择:
- 一致
Zip [1,3,4]
因为它是第一个 - 与Maybe- 醇>
Zip [10,20,30,40]
因为它最长 - 与Zip []
被弃置一致
其中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
的情况下生成的唯一a
是Zip [] :: 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 [])
答案 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
是否有任何理由遵循该惯例。
没有明智的some
或many
我不相信,虽然很少Alternative
实际上有这些 - 也许他们被更好地隔离到{{1的子类中}}
Alternative
隐含的“替代实例”没有任何信心。也许最好看看这种“扩展”实例可以应用于哪些地方(树?)并将其写为库。
答案 3 :(得分:0)
事实上,ZipList有一个明智的Alternative
实例。它来自a paper on free near-semirings(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