尾递归函数被编译成一个循环,它们不使用任何依赖于迭代次数的额外堆栈。
las,您的版本不是尾递归的,因为您的运算符优先级错误。累加器r
被解释为属于递归调用,它被原封不动地传递给它。因此,该函数需要返回以使其返回值递增。
让我们来看看:
let rec len xs =
match xs with
| [] -> 0
| x::xr -> 1 + len xr;;
let rec leni xs r =
match xs with
| [] -> r
| x::xr -> leni xr r+1;;
[0..10000] |> len // val it : int = 10001
[0..100000] |> len // val it : int = 100001
[0..1000000] |> len // Process is terminated due to StackOverflowException.
([0..1000000], 0) ||> leni // Process is terminated due to StackOverflowException.
解决方法是简单地将新的累加器值括在括号中,将其加 1。
let rec leni' xs r =
match xs with
| [] -> r
| x::xr -> leni' xr (r+1)
([0..1000000], 0) ||> leni' // val it : int = 1000001
您可以更进一步并使用连续传递样式 (CPS),将累加器替换为组合函数,每个函数的参数都加 1。这也将编译成一个循环并保留堆栈空间,代价是存储函数链所需的内存。
此外,您可以重新考虑参数的顺序:首先使用累加器(或延续),最后使用列表允许使用function
关键字。
let rec lenk k = function
| [] -> k 0
| x::xr -> lenk (k << (+) 1) xr
[0..1000000] |> lenk id // val it : int = 1000001