为什么foldr适用于无限列表?

时间:2015-06-06 18:42:19

标签: haskell recursion fold

此功能可以在无限关联列表中使用,很容易找出原因:

findKey :: (Eq k) => k -> [(k,v)] -> Maybe v  
findKey key [] = Nothing  
findKey key ((k,v):xs) = if key == k  
                         then Just v  
                         else findKey key xs

当找到密钥时,它返回Just v,停止递归。 现在看看另一个实现:

 findKey' :: (Eq k) => k -> [(k,v)] -> Maybe v  
 findKey' key = foldr (\(k,v) acc -> if key == k then Just v else acc) Nothing

编译器/解释器如何知道当密钥与k匹配时,它可以返回它?

 *Main> findKey' 1 $ zip [1..] [1..]

返回Just 1

当找到key == k时,它会返回Just v。为什么递归停在那里,允许我们用无限关联列表做这些事情?

2 个答案:

答案 0 :(得分:6)

因为传递给foldr的函数并不总是评估acc参数,即它在该参数中是惰性的。

例如,

(\(k,v) acc -> if 1 == k then Just v else acc) (1,"one") (error "here be dragons!")

将返回"one",甚至不会尝试评估error表达式。

此外,foldr按定义满足:

foldr f a (x:xs) = f x (foldr f a xs)

如果x:xs无限,但f不使用其第二个参数,则foldr可以立即返回。

在您的示例中,当{且仅当第一个参数不是想要的关联时,f计算其第二个元素。这意味着关联列表的评估仅足以找到key关联。

如果您想进行试验,请尝试以下方法:

foldr (\(k,v) acc -> case acc of
          Nothing -> if key == k then Just v else acc
          Just y  -> if key == k then Just v else acc) Nothing

case看起来多余,因为该函数在两个分支中返回相同的内容。 然而,这需要评估acc打破无限列表上的代码。

您可能想要尝试的另一件事

foldr (:) [] [0..]

这基本上重建了无限列表。

foldr (\x xs -> x*10 : xs) [] [0..]

这会将所有内容乘以10,相当于map (*10) [0..]

答案 1 :(得分:2)

foldr的非空案例可以定义为foldr f init (x:xs) = f x (foldr f init xs)。在您的情况下,f(\(k,v) acc -> if key == k then Just v else acc),因此(k,v)代表列表中的当前元素,acc代表(foldr f init xs)。也就是说,acc代表递归调用。在then - 情况下,您不使用acc,因此递归调用不会发生,因为Haskell是惰性的,意味着在使用之前不会评估参数。