我刚开始学习 F#,有一个基本问题。
这是代码:
let rec forLoop body times =
if times <= 0 then
()
else
body()
forLoop body (times - 1)
我不理解当你定义一个变量时,它是一个值且不可变的概念。在这里,为了循环,该值正在改变。那和C#中的变量有什么不同呢?
我刚开始学习 F#,有一个基本问题。
这是代码:
let rec forLoop body times =
if times <= 0 then
()
else
body()
forLoop body (times - 1)
我不理解当你定义一个变量时,它是一个值且不可变的概念。在这里,为了循环,该值正在改变。那和C#中的变量有什么不同呢?
它没有改变。您使用了递归。那个变量保持不变,但是它减去了一并传递给函数。在这种情况下,函数是相同的。
堆栈看起来像:
forLoop body 0
|
forLoop body 1
|
forLoop body 2
在C#中,所展示的代码不会被表示为for循环,而是递归的(类似于这样):
void ForLoop(int times, Action body)
{
if (times <= 0)
{
return;
}
else
{
body();
ForLoop(times - 1, body);
}
}
正如你所看到的,times
的值在任何时候都没有被改变。
times
实例都是内存中的不同对象。如果body()
以任何方式使用times
,它会捕获当前堆栈帧中的不可变值,这与后续递归调用中的值不同。using System;
using System.Threading;
class Program
{
static void ForLoop(int n)
{
while (n >= 0)
{
if (n == 100)
{
ThreadPool.QueueUserWorkItem((_) => { Console.WriteLine(n); });
}
n--;
}
}
static void Main(string[] args)
{
ForLoop(200);
Thread.Sleep(2000);
}
}
F#程序 - 总是打印100:
open System
open System.Threading
let rec forLoop times =
if times <= 0 then
()
else
if times = 100 then
ThreadPool.QueueUserWorkItem(fun _ ->
Console.WriteLine(times)) |> ignore
forLoop (times - 1)
forLoop 200
Thread.Sleep(2000)
这些差异是由于在 C# 代码中传递给 QueueUserWorkItem
的 lambda 捕获了一个可变变量,而在 F# 版本中它捕获了一个不可变值。
当您执行调用(任何调用)时,运行时会分配一个新的堆栈帧,并将被调用函数的参数和局部变量存储在新的堆栈帧中。当您执行递归调用时,分配的帧包含具有相同名称的变量,但这些变量存储在不同的堆栈帧中。
为了证明这一点,我将使用您示例的略微简化版本:
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
)。
times
时,我不会使用“对象”这个术语,因为据我所见它从未被装箱。 - kvb