[英]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 中时是:
Just (1 + 1)
)应用于 arguments 的惰性数据构造函数(例如Just (1 + 1)
)const 2
)部分应用的 function(例如const 2
)\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.