递归与折叠效率

时间:2018-11-26 06:29:32

标签: haskell recursion fold

在众所周知的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  

我发现这令人困惑。我说的对吗?

  1. 基于foldr的函数将始终遍历整个列表,然后生成结果,而第一个函数将在发现后立即停止?
  2. 结果是,第一个函数将在无限列表上工作,而第二个函数不会?

在我看来,真的等效定义将使用scanr代替,然后从中得到不是Nothing的第一个结果。 (?)

2 个答案:

答案 0 :(得分:4)

  1. 否,当\(k,v) acc -> ...调用函数foldr时,对foldr的递归调用将作为第二个参数(即acc)提供。因此,请记住,Haskell是惰性的,如果未使用acc(即,如果if条件为true),则不会发生递归调用并且遍历将停止。
  2. 两者在无限列表中都可以正常工作。

答案 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的值)。