在众所周知的Haskell tutorial中,首先定义在关联列表中按键查找值的函数,如下所示:
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
但是,作者随后认为,这种“教科书递归”类型最好使用折叠实现:
findKey key = foldr (\(k,v) acc -> if key == k then Just v else acc) Nothing
我发现这令人困惑。我说的对吗?
foldr
的函数将始终遍历整个列表,然后生成结果,而第一个函数将在发现后立即停止?在我看来,真的等效定义将使用scanr
代替,然后从中得到不是Nothing
的第一个结果。 (?)
答案 0 :(得分:4)
\(k,v) acc -> ...
调用函数foldr
时,对foldr
的递归调用将作为第二个参数(即acc
)提供。因此,请记住,Haskell是惰性的,如果未使用acc
(即,如果if
条件为true),则不会发生递归调用并且遍历将停止。答案 1 :(得分:4)
foldr
的定义如下
foldr cons z (x:xs) = cons x (foldr cons z xs)
因此,如果cons
不使用其第二个参数,则不需要其值。由于Haskell是按需调用的,因此不需要的值不会被评估。
因此,两种配方都具有相同的惰性特性。
findKey key (x:xs)
= foldr (\(k,v) r -> if key == k then Just v else r) Nothing (x:xs)
= (\(k,v) r -> if key == k then Just v else r) x
(foldr (\(k,v) r -> if key == k then Just v else r) Nothing xs)
= case x of (k,v) -> if key == k then Just v else
(foldr (\(k,v) r -> if key == k then Just v else r) Nothing xs)
因此,如果key == k
成立,则不会触发递归调用(以找出r
的值)。