Functional programming 长度函数的尾部递归版本是否在运行时节省堆栈空间?

Functional programming 长度函数的尾部递归版本是否在运行时节省堆栈空间?,functional-programming,f#,tail-recursion,space-efficiency,Functional Programming,F#,Tail Recursion,Space Efficiency,我被要求将这个F#length函数更改为尾部递归函数 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;; 与非尾部递归函数相比,确

我被要求将这个F#
length
函数更改为尾部递归函数

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;;

与非尾部递归函数相比,确实在运行时节省了堆栈空间?

尾部递归函数被编译成循环,它们不使用任何依赖于迭代次数的额外堆栈

唉,您的版本不是尾部递归的,因为运算符的优先级是错误的。累加器
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.
解决方法是简单地将新的累加器值括在parens中,再加上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。这也会编译成一个循环并保留堆栈空间,以牺牲存储函数链所需的内存为代价

此外,您还可以重新考虑参数的顺序:首先使用累加器(或continuation),最后使用列表,这样就可以使用
函数
关键字

let rec lenk k = function
| [] -> k 0 
| x::xr -> lenk (k << (+) 1) xr

[0..1000000] |> lenk id      // val it : int = 1000001
让rec lenk=函数
|[]->k 0
|x::xr->lenk(k lenk id//val it:int=1000001

尾部递归函数被编译成循环,它们不使用任何依赖于迭代次数的附加堆栈

遗憾的是,您的版本不是尾部递归的,因为运算符的优先级是错误的。累加器
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.
解决方法是简单地将新的累加器值括在parens中,再加上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。这还将编译成循环并保留堆栈空间,而代价是存储函数链所需的内存

此外,您还可以重新考虑参数的顺序:首先使用累加器(或continuation),最后使用列表,这样就可以使用
函数
关键字

let rec lenk k = function
| [] -> k 0 
| x::xr -> lenk (k << (+) 1) xr

[0..1000000] |> lenk id      // val it : int = 1000001
让rec lenk=函数
|[]->k 0
|x::xr->lenk(k lenk id//val it:int=1000001
@Bergi语言是用F#表示的。开头是指顶部显示的代码。很抱歉不清楚。@Bergi语言是用F#表示的。开头是指顶部显示的代码。很抱歉不清楚。