繁体   English   中英

递归或折叠haskell

[英]Recursion or fold in haskell

我正在学习一些使用 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一次,它的存在。

那么,这种妥协是否一直存在? 我是否误解了递归和折叠的工作方式?

foldr定义如下:

foldr k z = go
          where
            go []     = z
            go (y:ys) = y `k` go ys

如果您将此定义内联到all1 ,您将看到结果也是递归的。 因此,它只是没有明确递归,因为它隐藏了foldr内部的递归。

foldr变体既节省空间又节省时间,因为foldr具有列表融合规则(一种删除中间列表的优化), all1可以免费获得。

要使短路工作,只需将acc && px更改为px && acc 使用foldr ,一旦得到False结果,它就会停止遍历列表。 使用foldlfoldl' ,即使您的折叠功能短路,它仍然需要遍历列表的其余部分。

总结:使用foldrfoldlfoldl'或在您自己的函数中显式递归更有效。 一个很好的简单测试是在 GHCi 中执行+set :s ,然后比较列表上的性能(False:replicate 10000000 True)

让我们一砖一瓦地考虑基于foldr的解决方案是否可以短路。 首先, (&&)定义如下

(&&)                    :: Bool -> Bool -> Bool
True  && x              =  x
False && _              =  False

考虑到第二个子句,并且由于懒惰,如果第一个参数为False ,则(&&)的第二个参数将被忽略——换句话说,它短路了。

接下来, 这是列表的文件foldr

foldr            :: (a -> b -> b) -> b -> [a] -> b
foldr k z = go
          where
            go []     = z
            go (y:ys) = y `k` go ys

如果y `k` go ys不用看评价go ys ,就没有递归调用,以及折叠作为一个整体将快捷方式。

all1 ,二元运算是\\x acc -> acc && px 这对于我们的目的来说还不够好,因为将acc (对应于foldr定义中的go ys )作为第一个,短路, (&&)参数导致整个列表被消耗,而不管px结果是什么成为。 但是,并非所有内容都丢失了:将参数交换为(&&) ...

all3 :: (a -> Bool) -> [a] -> Bool
all3 p xs = foldr (\x acc -> p x && acc) True xs

... 为我们提供了所需的短路。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM