List 如何制作尾部递归函数

List 如何制作尾部递归函数,list,haskell,recursion,merge,tail-recursion,List,Haskell,Recursion,Merge,Tail Recursion,我对如何使函数“尾部递归”感到困惑 这是我的函数,但我不知道它是否已经是尾部递归的 我正在尝试合并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) merge2::Ord a=>[a]->[a]->

我对如何使函数“尾部递归”感到困惑

这是我的函数,但我不知道它是否已经是尾部递归的

我正在尝试合并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)
merge2::Ord a=>[a]->[a]->[a]->[a]
merge2 xs[]=xs
merge2[]ys=ys
merge2(x:xs)(y:ys)=如果y
您的函数不是尾部递归的;它是有保护的。然而,如果您想提高内存效率,那么在Haskell中应该使用保护递归

要使调用成为尾部调用,其结果必须是整个函数的结果。此定义适用于递归调用和非递归调用

例如,在代码中

f x y z = (x ++ y) ++ z
调用
(x++y)++z
是尾部调用,因为它的结果是整个函数的结果。调用
x++y
不是尾部调用

以尾递归为例,考虑<代码> FoLDL</代码>:

foldl :: (b -> a -> b) -> b -> [a] -> b
foldl _ acc []     = acc
foldl f acc (x:xs) = foldl f (f acc x) xs
递归调用foldl f(f acc x)xs是尾部递归调用,因为它的结果是整个函数的结果。因此,它是一个尾部调用,它是递归的,是对自身的
foldl
调用

代码中的递归调用

merge2 (x:xs) (y:ys) = if y < x then y : merge2 (x:xs) ys 
                                else x : merge2 xs (y:ys)
您可以将延迟求值视为发生在“外部-内部”。当对
foldl
的递归调用求值时,thunk在累加器中构建。因此,在惰性语言中,带累加器的尾部递归由于计算延迟而不具有空间效率(除非在进行下一个尾部递归调用之前立即强制累加器,从而防止thunks累积,并最终呈现已计算的值)

与尾部递归不同,您应该尝试使用保护递归,其中递归调用隐藏在惰性数据构造函数中。使用惰性求值,表达式将一直求值,直到它们处于弱头范式(WHNF)。表达式在WHNF中,如果它是:

  • 应用于参数的惰性数据构造函数(例如,
    Just(1+1)
  • 部分应用的功能(例如
    常数2
  • lambda表达式(例如
    \x->x
考虑
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:[])
表达式
(+1)1:map(+1)(2:3:[])
位于WHNF中,因为数据构造函数是
(:)
,因此计算在此点停止。您的
merge2
函数也使用保护递归,因此在惰性语言中它也是节省空间的

TL;DR:在惰性语言中,如果尾部递归在累加器中生成thunk,它仍然会占用内存,而保护递归不会生成thunk

有用链接:

相关:请特别注意WillemVanOnsem链接中的“保护递归”。您的
merge2
函数不是尾部递归,但其所有递归都是保护递归,这在Haskell中通常更为重要。
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:[])