【发布时间】:2023-04-09 19:55:01
【问题描述】:
我正在学习一些使用 haskell 的函数式编程,并且我正在尝试通过重新实现一些库函数来了解一些概念。
我有一个问题,主要是关于何时选择递归而不是迭代更好。例如,在重新实现“全部”功能时,我可以选择:
all1 :: (a -> Bool) -> [a] -> Bool
all1 p xs = foldr (\x acc -> acc && p x) True xs
all2 :: (a -> Bool) -> [a] -> Bool
all2 p (x:xs)
| p x == False = False
| otherwise = all2 p xs
在我看来,递归的应该更节省时间,因为它会在第一个不匹配的条目处停止,而折叠的更节省空间(我认为,仍然不清楚尾递归优化haskell) 但它总是会扫描完整列表,除非通过查看false 将始终给出false 的事实进行了一些巧妙的优化。
那么,这种妥协是一直存在的吗?我是否误解了递归和折叠工作的方式?
【问题讨论】:
-
Haskell 的这个领域(空间/时间效率)不是我的强项,但我认为如果你将折叠函数重写为
(\x acc -> p x && acc),那么如果它找到了一个False值,因为这个变体将[e1, e2, e3, e4, ...]变成p e1 && (p e2 && (p e3 && (p e4 && ...))),如果有任何返回False,它就会短路。 -
(1) 旁注:
foldr也是递归的。与使用foldr等函数的解决方案相比,谈论第二个解决方案的常用术语是“显式递归”。 (2) 正如@bradrn 所指出的,如果您将参数的顺序切换为(&&),您的第一个解决方案也会短路。在这里的答案中非常值得探索。 -
另一方面,Haskell 中的尾递归必须考虑到惰性。相关问答:Does Haskell have tail-recursive optimization?; foldl is tail recursive, so how come foldr runs faster than foldl?.
标签: haskell recursion lazy-evaluation