【问题标题】:Recursion vs fold efficiency递归与折叠效率
【发布时间】:2019-04-27 18:47:25
【问题描述】:

在著名的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 的第一个结果。 (?)

【问题讨论】:

  • 尝试在无限列表上使用基于 foldr 的版本。您是否必须等待无限时间才能得到结果?如果不是,则此版本不会遍历整个列表。

标签: haskell recursion fold


【解决方案1】:

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

【讨论】:

  • foldr-O2 通常最终比使用手动递归执行相同的操作具有更高的性能,这要归功于 fold/build 融合。
【解决方案2】:
  1. 不,当foldr 调用函数\(k,v) acc -> ... 时,对foldr 的递归调用作为第二个参数提供(即acc)。因此,请记住 Haskell 是惰性的,如果不使用 acc(即如果 if 条件为真),则不会发生递归调用并且遍历停止。
  2. 两者都适用于无限列表。

【讨论】:

  • @sepp2k 终于明白你的意思了,谢谢!你介意添加一个例子吗?我相信它会对未来的读者有用。
猜你喜欢
  • 2016-11-19
  • 2012-04-13
  • 2015-07-18
  • 2012-03-12
  • 1970-01-01
  • 2012-02-12
  • 2016-10-29
  • 1970-01-01
  • 2023-04-09
相关资源
最近更新 更多