简体   繁体   English

如何制作尾递归 function

[英]How to make a tail recursive function

I am really confused on how to make a function "Tail recursive".我真的很困惑如何制作 function “尾递归”。

Here is my function, but I don't know whether it is already tail recursive or not.这是我的function,但我不知道它是否已经是尾递归了。

I am trying to merge two lists in Haskell.我正在尝试合并 Haskell 中的两个列表。

merge2 :: Ord a =>[a]->[a]->[a]
merge2 xs [] = xs
merge2 [] ys = ys
merge2 (x:xs)(y:ys) = if y < x then y: merge2 (x:xs) ys else x :merge2 xs (y:ys)

Your function isn't tail-recursive;您的 function 不是尾递归的; it's guarded recursive.它是递归的。 However, guarded recursion is what you should be using in Haskell if you want to be memory efficient.但是,如果您想提高 memory 的效率,那么您应该在 Haskell 中使用受保护的递归。

For a call to be a tail call, its result must be the result of the entire function.一个调用要成为尾调用,其结果必须是整个 function 的结果。 This definition applies to both recursive and non-recursive calls.此定义适用于递归和非递归调用。

For example, in the code例如,在代码中

f x y z = (x ++ y) ++ z

the call (x ++ y) ++ z is a tail call because its result is the result of the entire function.调用(x ++ y) ++ z是尾调用,因为它的结果是整个 function 的结果。 The call x ++ y is not a tail call.调用x ++ y不是尾调用。

For an example of tail-recursion, consider foldl :对于尾递归的示例,请考虑foldl

foldl :: (b -> a -> b) -> b -> [a] -> b
foldl _ acc []     = acc
foldl f acc (x:xs) = foldl f (f acc x) xs

The recursive call foldl f (f acc x) xs is a tail-recursive call because its result is the result of the entire function.递归调用foldl f (f acc x) xs是尾递归调用,因为它的结果是整个 function 的结果。 Thus it's a tail call, and it is recursive being a call of foldl to itself.因此,这是一个尾调用,并且它是对自身的foldl调用。

The recursive calls in your code代码中的递归调用

merge2 (x:xs) (y:ys) = if y < x then y : merge2 (x:xs) ys 
                                else x : merge2 xs (y:ys)

are not tail-recursive because they do not give the result of the entire function.不是尾递归的,因为它们没有给出整个 function 的结果。 The result of the call to merge2 is used as a part of the whole returned value, a new list.merge2的调用结果用作整个返回值的一部分,即一个新列表。 The (:) constructor, not the recursive call, gives the result of the entire function. (:)构造函数,而不是递归调用,给出了整个 function 的结果。 And in fact, being lazy, (:) _ _ returns right away, and the holes _ are filled only later, if and when needed.事实上,由于懒惰, (:) _ _会立即返回,而空洞_仅在需要时才被填充。 That's why guarded recursion is space efficient.这就是为什么受保护的递归是节省空间的。

However, tail-recursion doesn't guarantee space efficiency in a lazy language.然而,尾递归并不能保证惰性语言的空间效率。 With lazy evaluation, Haskell builds up thunks , or structures in memory that represent code that is yet to be evaluated.通过惰性评估,Haskell 会在 memory 中构建thunk或结构,这些结构表示尚未评估的代码。 Consider the evaluation of the following code:考虑对以下代码的评估:

foldl f 0 (1:2:3:[])
=> foldl f (f 0 1) (2:3:[])
=> foldl f (f (f 0 1) 2) (3:[])
=> foldl f (f (f (f 0 1) 2) 3) []
=> f (f (f 0 1) 2) 3

You can think of lazy evaluation as happening "outside-in."您可以将惰性评估视为“由外而内”发生的事情。 When the recursive calls to foldl are evaluated, thunks are built-up in the accumulator.当评估对foldl的递归调用时,会在累加器中建立 thunk。 So, tail recursion with accumulators is not space efficient in a lazy language because of the delayed evaluation (unless the accumulator is forced right away, before the next tail-recursive call is made, thus preventing the thunks build-up and instead presenting the already-calculated value, in the end).因此,使用累加器的尾递归在惰性语言中的空间效率不高,因为延迟评估(除非在进行下一次尾递归调用之前立即强制累加器,从而防止 thunk 累积,而是呈现已经-计算值,最后)。

Rather than tail recursion, you should try to use guarded recursion, where the recursive call is hidden inside a lazy data constructor.您应该尝试使用受保护的递归,而不是尾递归,其中递归调用隐藏在惰性数据构造函数中。 With lazy evaluation, expressions are evaluated until they are in weak head normal form (WHNF).使用惰性求值时,表达式会被求值,直到它们处于弱头范式(WHNF) 中。 An expression is in WHNF when it is either:表达式在 WHNF 中时是:

  • A lazy data constructor applied to arguments (eg Just (1 + 1) )应用于 arguments 的惰性数据构造函数(例如Just (1 + 1)
  • A partially applied function (eg const 2 )部分应用的 function(例如const 2
  • A lambda expression (eg \x -> x )一个 lambda 表达式(例如\x -> x

Consider map :考虑map

map :: (a -> b) -> [a] -> [b]
map _ []     = []
map f (x:xs) = f x : map f xs

map (+1) (1:2:3:[])
=> (+1) 1 : map (+1) (2:3:[])

The expression (+1) 1: map (+1) (2:3:[]) is in WHNF because of the (:) data constructor, and therefore evaluation stops at this point.由于(:)数据构造函数,表达式(+1) 1: map (+1) (2:3:[])在 WHNF 中,因此此时停止计算。 Your merge2 function also uses guarded recursion, so it too is space-efficient in a lazy language.您的merge2 function 也使用受保护的递归,因此它在惰性语言中也很节省空间。

TL;DR: In a lazy language, tail-recursion can still take up memory if it builds up thunks in the accumulator, while guarded recursion does not build up thunks. TL;DR:在惰性语言中,如果尾递归在累加器中建立 thunk,它仍然可以占用 memory,而受保护的递归不会建立 thunk。

Helpful links:有用的网址:

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

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