简体   繁体   English

Haskell中的foldR尾递归吗?

[英]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 将:

  • change the semantics when dealing with partially-defined lists ( 1:2:undefined ) or infinite ones ( [0..] );在处理部分定义的列表( 1:2:undefined )或无限列表( [0..] )时更改语义;
  • be less efficient on finite length lists, which would always have to be completely scanned, even when there is no need to.在有限长度列表上效率较低,即使没有必要,也总是必须完全扫描。

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本身不是尾递归,但取决于ffoldr 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.

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