我刚开始使用 F#,有一个基本问题。
这是代码:
let rec forLoop body times =
if times <= 0 then
()
else
body()
forLoop body (times - 1)
我不明白当你定义一个变量时它是一个值并且不可变的概念。在这里,该值正在更改以循环。这与 C# 中的变量有何不同?
最佳答案
当您执行调用(任何调用)时,运行时会分配一个新的堆栈帧并将被调用函数的参数和局部变量存储在新的堆栈帧中。当您执行递归调用时,分配的帧包含具有相同名称的变量,但这些变量存储在不同的堆栈帧中。
为了证明这一点,我将使用您示例的稍微简化的版本:
let rec forLoop n =
if times > 0 then
printf "current %d" n
forLoop body (n - 1)
现在,假设我们从程序的某个顶级函数或模块调用
forLoop 2
。运行时为调用分配堆栈并将参数值存储在表示 forLoop
调用的帧中:+----------------------+
| forLoop with n = 2 |
+----------------------+
| program |
+----------------------+
forLoop
函数打印 2
并继续运行。它对 forLoop 1
执行递归调用,分配一个新的堆栈帧:+----------------------+
| forLoop with n = 1 |
+----------------------+
| forLoop with n = 2 |
+----------------------+
| program |
+----------------------+
由于
1 > 0
程序再次进入 then
分支,打印 1
并再次递归调用 forLoop
函数:+----------------------+
| forLoop with n = 0 |
+----------------------+
| forLoop with n = 1 |
+----------------------+
| forLoop with n = 2 |
+----------------------+
| program |
+----------------------+
此时,
forLoop
函数返回,没有进行任何其他调用,并且随着程序从所有递归调用返回,堆栈帧被一一移除。从图中可以看出,我们创建了三个不同的变量,这些变量存储在不同的堆栈帧中(但它们都被命名为 n
)。还值得注意的是,F# 编译器执行各种优化,例如尾调用,它可以使用可变变量(效率更高)来替换调用和新堆栈帧的分配。但是,这只是一种优化,如果您想了解递归的心理模型,则无需担心。
关于f# - F# 中的不可变值,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2623971/