我试图自己理解这个优雅的解决方案,所以我尝试自己推导类型和评估。所以,我们需要写一个函数:
zip xs ys = foldr step done xs ys
这里我们需要导出step 和done,不管它们是什么。回忆foldr 的类型,实例化为列表:
foldr :: (a -> state -> state) -> state -> [a] -> state
但是,我们的foldr 调用必须实例化为如下所示,因为我们必须接受的不是一个,而是两个列表参数:
foldr :: (a -> ? -> ?) -> ? -> [a] -> [b] -> [(a,b)]
因为-> 是right-associative,这相当于:
foldr :: (a -> ? -> ?) -> ? -> [a] -> ([b] -> [(a,b)])
我们的([b] -> [(a,b)])对应于原始foldr类型签名中的state类型变量,因此我们必须用它替换每个出现的state:
foldr :: (a -> ([b] -> [(a,b)]) -> ([b] -> [(a,b)]))
-> ([b] -> [(a,b)])
-> [a]
-> ([b] -> [(a,b)])
这意味着我们传递给foldr 的参数必须具有以下类型:
step :: a -> ([b] -> [(a,b)]) -> [b] -> [(a,b)]
done :: [b] -> [(a,b)]
xs :: [a]
ys :: [b]
回想一下 foldr (+) 0 [1,2,3] 扩展为:
1 + (2 + (3 + 0))
因此,如果xs = [1,2,3] 和ys = [4,5,6,7],我们的foldr 调用将扩展为:
1 `step` (2 `step` (3 `step` done)) $ [4,5,6,7]
这意味着我们的1 `step` (2 `step` (3 `step` done)) 构造必须创建一个递归函数,该函数将通过[4,5,6,7] 并压缩元素。 (请记住,如果原始列表之一较长,则多余的值将被丢弃)。 IOW,我们的构造必须具有 [b] -> [(a,b)] 类型。
3 `step` done 是我们的基本情况,其中done 是一个初始值,如foldr (+) 0 [1..3] 中的0。我们不想在 3 之后压缩任何东西,因为 3 是 xs 的最终值,所以我们必须终止递归。在基本情况下如何终止对列表的递归?您返回空列表[]。但回想一下done 类型签名:
done :: [b] -> [(a,b)]
因此我们不能只返回[],我们必须返回一个忽略它接收到的任何东西的函数。因此使用const:
done = const [] -- this is equivalent to done = \_ -> []
现在让我们开始弄清楚step 应该是什么。它将a 类型的值与[b] -> [(a,b)] 类型的函数组合并返回[b] -> [(a,b)] 类型的函数。
在3 `step` done 中,我们知道稍后将进入我们压缩列表的结果值必须是(3,6)(从原始xs 和ys 得知)。因此3 `step` done 必须计算为:
\(y:ys) -> (3,y) : done ys
记住,我们必须返回一个函数,在其中我们以某种方式压缩元素,上面的代码是有意义的和类型检查的。
现在我们假设step 应该如何评估,让我们继续评估。以下是我们foldr 评估中所有减少步骤的样子:
3 `step` done -- becomes
(\(y:ys) -> (3,y) : done ys)
2 `step` (\(y:ys) -> (3,y) : done ys) -- becomes
(\(y:ys) -> (2,y) : (\(y:ys) -> (3,y) : done ys) ys)
1 `step` (\(y:ys) -> (2,y) : (\(y:ys) -> (3,y) : done ys) ys) -- becomes
(\(y:ys) -> (1,y) : (\(y:ys) -> (2,y) : (\(y:ys) -> (3,y) : done ys) ys) ys)
评估产生了这个步骤的实现(请注意,我们通过返回一个空列表来说明ys提前用完元素):
step x f = \[] -> []
step x f = \(y:ys) -> (x,y) : f ys
因此,完整的函数zip实现如下:
zip :: [a] -> [b] -> [(a,b)]
zip xs ys = foldr step done xs ys
where done = const []
step x f [] = []
step x f (y:ys) = (x,y) : f ys
P.S.:如果您受到褶皱优雅的启发,请阅读 Writing foldl using foldr,然后阅读 Graham Hutton 的 A tutorial on the universality and expressiveness of fold。