[英]Immutable values in F#
我剛剛開始使用F#,並且有一個基本問題。
這是代碼:
let rec forLoop body times =
if times <= 0 then
()
else
body()
forLoop body (times - 1)
我不知道如何在定義變量時將其定義為值並且是不可變的。 在這里,值不斷變化以便循環。 這與C#中的變量有何不同?
它沒有改變。 您使用遞歸。 該變量保持不變,但是將其減去1並傳遞給函數。 在這種情況下,功能是相同的。
堆棧看起來像
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
都不會改變。
每個遞歸調用中每個times
實例都是內存中的一個不同對象。 如果body()
以任何方式使用times
,它將從當前堆棧幀中捕獲不可變值,該值與后續遞歸調用中的值不同。
下面是C#和F#程序,它們顯示出差異可能很重要的一種方法。
C#程序-打印一些隨機數:
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
)。
還值得注意的是,F#編譯器執行了各種優化,例如tail-call ,它可以使用可變變量來替換調用和分配新的堆棧幀(更有效)。 但是,這只是一種優化,如果您想了解遞歸的心理模型,則無需擔心。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.