[英]instance Alternative ZipList in Haskell?
ZipList
带有一个Functor
和一个Applicative
实例 ( Control.Applicative ) 但为什么不是Alternative
呢?
Bool
可以有两种方式幺半群),因此也不应该是实例?我搜索了“instance Alternative ZipList”(用引号先找到代码),只找到了库、一些教程、讲义,但没有实际实例。
Matt Fenwick 说ZipList A
只会是一个幺半群,如果A
是( 见这里)。 不管元素类型如何,列表都是幺半群。
AndrewC对同一问题的另一个回答讨论了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)
我们可以在不知道类型参数a
情况下生成的唯一Zip a
是Zip [] :: Zip a
,因此对于empty
几乎没有选择。 如果空列表是幺半群的中性元素,我们可能会倾向于使用列表连接。 但是, 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] []
ziplists 背后的一个直觉是过程:有限或无限的结果流。 压缩时,我们组合流,这由Applicative
实例反映。 当到达列表末尾时,流不会产生更多元素。 这就是Alternative
实例派上用场的地方:我们可以命名一个并发替换(替代,真的),在默认进程终止后立即接管。
例如,我们可以编写fmap Just foo <|> pure Nothing
将 ziplist foo
每个元素包装到Just
然后继续使用Nothing
。 生成的 ziplist 是无限的,在所有(真实)值用完后恢复为默认值。 这当然可以通过在Zip
构造函数中附加一个无限列表来手动完成。 然而以上更优雅,并且不假设构造函数的知识,从而导致更高的代码可重用性。
我们不需要对元素类型做任何假设(比如作为幺半群本身)。 同时,定义并不简单(就像(<|>) = const
那样)。 它通过对第一个参数进行模式匹配来使用列表结构。
上面给出的<|>
的定义是关联的,空列表实际上是空元素。 我们有
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
所以你可以要求的所有法律都得到满足(列表串联不是这样)。
此实例与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 [])
有趣的。 一个并非完全不相关的想法:ZipList 可以被视为普通列表,其中元素由它们在列表中的(递增)位置索引标记。 压缩应用程序通过配对相同索引的元素来连接两个列表。
想象一下带有由(非递减) Ord
values标记的元素的列表。 Zippery应用程序将配对相同标记的元素,丢弃所有不匹配的元素(它有它的用途); zippery替代方案可以对标签值执行保持顺序的左优先联合(常规列表上的替代方案也是一种联合)。
这完全符合您对索引列表(又名 ZipLists)的建议。
所以是的,这是有道理的。
对值列表的一种解释是不确定性,这与列表的 monad 实例一致,但 ZipLists 可以解释为按顺序组合的同步值流。
有了这种流解释,您就不会考虑整个列表,因此选择最长的流显然是在作弊,并且从定义<|>
的第一个 ZipList 故障转移到第二个的正确解释将是正如您在实例中所说的那样,在第一个完成时即时完成。
将两个列表压缩在一起并不仅仅是因为类型签名,而是对<|>
的正确解释。
当您将两个列表压缩在一起时,结果是两个长度中的最小值。 这是因为这是在不使用 ⊥ 的情况下满足类型签名的最长可能列表。 将其视为选择两个长度中较短的一个是错误的 - 它是可能的最长的。
类似地, <|>
应该生成尽可能长的列表,并且它应该更喜欢左边的列表。 显然,它应该占用整个左侧列表并占用左侧列表中的右侧列表以保持同步/快速。
你的实例没问题,但它做了一些 ZipList 没有的事情
(a)瞄准最长的列表,以及
(b)在源列表之间混合元素。
压缩作为操作在最短列表的长度处停止。
这就是为什么我在回答中得出结论:
因此,唯一合理的替代实例是:
instance Alternative Zip where
empty = Zip []
Zip [] <|> x = x
Zip xs <|> _ = Zip xs
这与 Maybe 和解析器的 Alternative 实例一致,它们说如果没有失败就应该做a
如果失败就去b
。 您可以说较短的列表不如较长的列表成功,但我认为您不能说非空列表完全失败。
empty = Zip []
被选中是因为它在列表的元素类型中必须是多态的,并且唯一的这样的列表是[]
为了平衡,我不认为你的实例很糟糕,我认为这更干净,但是嘿嘿,根据需要推出自己的实例!
事实上,ZipList 有一个明智的Alternative
实例。 它来自一篇关于免费MonadPlus
的论文( 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
我对Alternative
指导直觉来自解析器,它表明如果您的替代品的一个分支以某种方式失败了,它应该被根除,从而导致一个Longest
风格的Alternative
可能不是非常有用。 这将是无偏见的(与解析器不同)但在无限列表上失败。
再说一次,正如您所建议的,他们必须做的就是形成Monoid
。 但是,您的偏向以ZipList
通常不会体现的方式存在——您可以同样轻松地清晰地形成Alternative
实例的反映版本。 正如您所指出的,这也是与Maybe
的约定,但我不确定ZipList
是否有任何理由遵循该约定。
没有明智的some
或many
我不相信,尽管实际上很少有Alternative
具有这些 - 也许它们最好被隔离到Alternative
的子类中。
坦率地说,我不认为您的建议是一个糟糕的实例,但我对它是ZipList
所暗示的“替代实例”没有任何信心。 也许最好看看这种“扩展”实例还可以在哪里应用(树?)并将其编写为库。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.