Canonical外部连接拉链功能

时间:2012-02-08 17:29:11

标签: haskell zip outer-join

如果将列表中每个元素的(隐式)索引视为其键,则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]

我发现自己经常需要它,我宁愿使用一个常用的习惯用法来使我的代码更易于编写(并且更易于维护),而不是实现outerZipWith或执行if length as < length bs then zipWith f (as ++ repeat a) bs else zipWith f as (bs ++ repeat b)

3 个答案:

答案 0 :(得分:8)

这种事情出现了很多。这是PACT algebracogroup操作。在我工作的地方,我们使用类型类来区分这三个操作:

  1. zip:结构交叉点。
  2. align:结构联盟。
  3. liftA2:结构性笛卡尔积。
  4. 这由Paul Chiusano on his blog讨论。

答案 1 :(得分:5)

这看起来很尴尬,因为你试图跳到最后而不是处理原始操作。

首先,zipWith在概念上是zip,后跟map (uncurry ($))。这是一个重要的观点,因为(un)currying是zipWith可能的原因。给定类型为[a][b]的列表,要应用函数(a -> b -> c)并获取类型为[c]的内容,您显然需要每个输入中的一个。执行此操作的两种方法恰好是列表的两个Applicative实例,其中一个zipWithliftA2。 (另一个例子是标准的,它给出了笛卡尔积 - 如果你愿意的话,就是交叉连接。)

您想要的语义与任何明显的Applicative实例都不对应,这就是为什么它要困难得多。首先考虑outerZip :: [a] -> [b] -> [?? a b]以及它将具有什么类型。结果列表的元素可以分别是ab或两者。这不仅不符合任何标准数据类型,因此无法用表达式(A + B + A*B)来表示任何有用的东西。

这种数据类型有自己的用途,这就是我my own package defining one的原因。我记得还有一个关于hackage(我认为辅助功能比我的少),但我不记得它叫什么。

坚持标准的东西,你最终需要一个合理的“默认值”,大致转化为拥有Monoid实例并使用标识值填充列表。但是,使用Monoid包装器等对适当的newtype进行转换可能不会比当前的实现更简单。


顺便说一句,您对列表索引作为键的评论实际上可以进一步发展;具有类似密钥概念的任何FunctorReader monad是同构的,a.k.a。是从键到值的显式函数。 Edward Kmett一如既往地拥有一堆编码这些抽象概念的软件包,在本例中是从the representable-functors package构建的。可能会有所帮助,如果你不介意写一打实例,至少要开始......

答案 2 :(得分:3)

为什么不提供一个值列表,直到达到更长列表的末尾,而不是用常量填写较短的列表?这也消除了对Maybe的需要,因为列表可以是空的(或有限长度)。

我无法找到任何标准内容,但如果没有按照您展示的方式完全重新实施zipWith,我就开发了这样的length测试版本:

outerZipWith :: (a -> b -> c) -> [a] -> [b] -> [a] -> [b] -> [c]
outerZipWith f as bs as' bs' = take n $ zipWith f (as ++ as') (bs ++ bs')
  where n = max (length as) (length bs)