如果您将列表中每个元素的(隐式)索引视为其键,则zipWith类似于关系内部联接。它仅处理两个输入都具有值的键:

zipWith (+) [1..5] [10..20] == zipWith (+) [1..11] [10..14] == [11,13,15,17,19]

是否有对应于外部联接的规范对应函数?就像是:
outerZipWith :: (a -> b -> c) -> a -> b -> [a] -> [b] -> [c]
outerZipWith _ _ _ [] [] = []
outerZipWith f a' b' [] (b:bs) = f a' b : outerZipWith f a' b' [] bs
outerZipWith f a' b' (a:as) [] = f a b' : outerZipWith f a' b' as []
outerZipWith f a' b' (a:as) (b:bs) = f a b : outerZipWith f a' b' as bs

或者可能
outerZipWith' :: (a -> b -> c) -> Maybe a -> Maybe b -> [a] -> [b] -> [c]
outerZipWith' _ _ _ [] [] = []
outerZipWith' _ Nothing _ [] _ = []
outerZipWith' _ _ Nothing _ [] = []
outerZipWith' f a' b' [] (b:bs) = f (fromJust a') b : outerZipWith f a' b' [] bs
outerZipWith' f a' b' (a:as) [] = f a (fromJust b') : outerZipWith f a' b' as []
outerZipWith' f a' b' (a:as) (b:bs) = f a b : outerZipWith f a' b' as bs

所以我可以做
outerZipWith (+) 0 0 [1..5] [10..20]  == [11,13,15,17,19,15,16,17,18,19,20]
outerZipWith (+) 0 0 [1..11] [10..14] == [11,13,15,17,19,6,7,8,9,10,11]

我发现自己有时会需要它,我宁愿使用一个通用的惯用法来使我的代码更可写(并且更易于维护),而不是实现outerZipWithif length as < length bs then zipWith f (as ++ repeat a) bs else zipWith f as (bs ++ repeat b)

最佳答案

这似乎很尴尬,因为您试图跳到最后而不是处理原始操作。

首先,zipWith从概念上讲是zip,后跟map (uncurry ($))。这是重要的一点,因为(un)currying是zipWith完全可用的原因。给定类型为[a][b]的列表,要应用函数(a -> b -> c)并获得[c]类型的内容,您显然需要每个输入之一。做到这一点的两种方法恰好是列表的两个Applicative实例,而zipWith是其中一个的liftA2。 (另一个实例是标准实例,它提供笛卡尔乘积-交叉连接,如果您愿意的话)。

您想要的语义与任何明显的Applicative实例都不对应,这就是为什么它更加困难的原因。首先考虑一个outerZip :: [a] -> [b] -> [?? a b]及其类型。结果列表的元素可以分别是ab或两者。这不仅不对应任何标准数据类型,而且用它们来表达很尴尬,因为您不能从(A + B + A*B)表达式中排除任何有用的东西。

这样的数据类型有其自己的用途,这就是为什么我有my own package defining one的原因。我记得也有一种黑客技术(我认为辅助功能少于我的),但我不记得它的名字。

仅仅停留在标准内容上,您最终需要一个明智的“默认值”,该值粗略地转换为具有Monoid实例并使用标识值来填充列表。但是,使用Monoid包装器或类似方法与适当的newtype进行来回转换可能不会比您当前的实现更简单。

顺便说一句,您关于列表索引作为键的评论实际上可以得到进一步发展。任何具有相似键概念的FunctorReader monad是同构的,也就是从键到值的显式函数。与往常一样,Edward Kmett有一堆编码这些抽象概念的软件包,在这种情况下,它们是从the representable-functors package构建的。如果您不介意至少为入门而编写十二个实例,可能会有所帮助...

08-04 04:22