此功能可以在无限关联列表中使用,很容易找出原因:
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
。为什么递归停在那里,允许我们用无限关联列表做这些事情?
答案 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是惰性的,意味着在使用之前不会评估参数。