[英]Is foldR Tail Recursive in Haskell?
I'm new to Haskell and reading Haskell from first principles, in chapter Folds page 384 I have came across FoldR and seems like its not tail recursive我是 Haskell 的新手,并从第一原理阅读 Haskell,在第 384 页的折叠章节中我遇到了FoldR ,似乎它不是尾递归的
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
1- can we make it tail recursive? 1-我们可以让它尾递归吗?
2- and do it will be optimized? 2-它会被优化吗?
In a lazy language like Haskell, tail recursion is often a bad idea.在像 Haskell 这样的惰性语言中,尾递归通常是个坏主意。 This is one of these cases.
这是其中一种情况。
We might attempt to make foldr
tail recursive by eg starting with a reverse
(which is also doable in a tail-recursive way), and then accumulating the foldr
result step by step, in a tail recursive way.我们可能会尝试通过例如从
reverse
开始(这也可以以尾递归方式实现)来使foldr
尾递归,然后以尾递归方式逐步累积foldr
结果。 However, that would break the foldr
semantics.但是,这会破坏
foldr
语义。
With the standard foldr
, we have eg使用标准的
foldr
,我们有例如
foldr (\_ _ -> k1) k2 (x:xs) = k1
no matter what xs
is, including a bottom value like undefined
or an infinite list like [0..]
.无论
xs
是什么,包括像undefined
这样的底部值或像[0..]
这样的无限列表。 Further, when xs
is a finite but long list, the code above is also efficient, since it stops the computation immediately without scanning the whole list.此外,当
xs
是一个有限但很长的列表时,上面的代码也很有效,因为它会立即停止计算而不扫描整个列表。
As a more practical example,作为一个更实际的例子,
and :: [Bool] -> Bool
and = foldr (&&) True
makes and xs
return False
as soon as some element of xs
evaluates to False
, without scanning the rest of the list.一旦
xs
的某些元素评估为False
, make and xs
返回False
,而不扫描列表的 rest 。
Concluding, turning foldr
into a tail recursive function would:最后,将
foldr
变成尾递归 function 将:
1:2:undefined
) or infinite ones ( [0..]
);1:2:undefined
)或无限列表( [0..]
)时更改语义; foldr
is not tail recursive... but it can be used to write functions that process lists in constant space. foldr
不是尾递归的......但它可用于编写在恒定空间中处理列表的函数。 chi already pointed out that it can implement and
efficiently. chi已经指出它可以有效
and
实施。 Here's how it can implement an efficient function for summing a list:以下是它如何实现高效的 function 来对列表求和:
mySum :: Num a => [a] -> a
mySum xs = foldr go id xs 0
where
go x r acc = r $! x + acc
How's this work?这个工作怎么样? Consider
mySum [1,2,3]
.考虑
mySum [1,2,3]
。 This expands to这扩展到
foldr go id [1,2,3] 0
==> -- definition of foldr
go 1 (foldr go id [2,3]) 0
==> -- definition of go
foldr go id [2,3] $! 1 + 0
==> -- strict application
foldr go id [2,3] 1
We've reduced the list size by one, and haven't accumulated anything on the "stack".我们已经将列表大小减少了 1,并且没有在“堆栈”上积累任何东西。 The same process repeats till we get
重复相同的过程,直到我们得到
foldr go id [] 6
==> definition of foldr
id 6
==> definition of id
6
Note: if this code is compiled by GHC with optimizations enabled ( -O
or -O2
), then it will actually transform it into blazing-fast tail-recursive code without any further assistance from you.注意:如果此代码由 GHC 编译并启用了优化(
-O
或-O2
),那么它实际上会将其转换为极快的尾递归代码,而无需您提供任何进一步的帮助。 But even unoptimized, it will work okay and not burn up a bunch of memory/stack.但即使未经优化,它也可以正常工作,不会烧掉一堆内存/堆栈。
foldr
is not tail recursive. foldr
不是尾递归的。 Sometime it is called the true "recursive" fold while fold left is "iterative" (because of tail recursion it is equivalent to iteration).有时它被称为真正的“递归”折叠,而左折叠是“迭代的”(因为尾递归,它等同于迭代)。
Please note that in Haskell, because of laziness, also foldl
do not guarantee constant space, that is why it exists foldl'
请注意,在 Haskell 中,由于懒惰,
foldl
也不保证恒定空间,这就是它存在的原因foldl'
foldr
is not tail recursive itself, but depending on f
, it is possible that foldr f
is tail recursive. foldr
本身不是尾递归,但取决于f
, foldr f
可能是尾递归。 Tail recursion in Haskell is quite subtle because of lazy evaluation.由于延迟评估,Haskell 中的尾递归非常微妙。
For example, consider f = (&&)
.例如,考虑
f = (&&)
。 In this case, we have在这种情况下,我们有
foldr (&&) acc lst =
case lst of
[] -> acc
(x:xs) -> x && foldr (&&) acc xs
=
case lst of
[] -> acc
(x:xs) -> if x
then foldr (&&) acc xs
else False
=
case lst of
[] -> acc
(x:xs) -> case x of
True -> foldr (&&) acc xs
False -> False
Note that in this case, we clearly see that foldr (&&)
is tail-recursive.请注意,在这种情况下,我们清楚地看到
foldr (&&)
是尾递归的。 In fact, foldr (||)
is also tail recursive.其实
foldr (||)
也是尾递归的。 Note that the fact that foldr (&&)
is tail recursive is fundamentally because of laziness.请注意,
foldr (&&)
是尾递归的事实基本上是因为懒惰。 If it weren't for laziness, we would have to evaluate foldr (&&) acc xs
before substituting the result into x && foldr (&&) acc xs
.如果不是因为懒惰,我们必须先评估
foldr (&&) acc xs
,然后再将结果代入x && foldr (&&) acc xs
。 But because of laziness, we evaluate x
first and only then determine whether we need to call foldr (&&) acc xs
, and whenever we make that call, it's a tail call.但是由于懒惰,我们首先评估
x
,然后才确定是否需要调用foldr (&&) acc xs
,并且每当我们进行该调用时,它都是尾调用。
However, in most cases, foldr f
will not be a tail recursive function.但是,在大多数情况下,
foldr f
不会是尾递归 function。 In particular, foldr ((+):: Int -> Int -> Int)
is not tail recursive.特别是,
foldr ((+):: Int -> Int -> Int)
不是尾递归的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.