Share via


本文章是由機器翻譯。

深究 CLR

F# 基礎

Luke Hoban

F # 是 Microsoft.NET] Framework 一新、 功能和物件導向程式設計語言,以及整合的 Microsoft Visual Studio 2010 今年 ’s 發行。 F # 強式靜態輸入文字,結合簡單、 簡潔的語法,並將 [F # 互動式大規模的.NET Framework 為基礎元件開發最多為使用 Visual Studio 中的輕量級 explorative 程式設計。

F # 被為了從地面上在 CLR 上執行。 使用.NET Framework 為基礎] 語言為 F # 會運用.NET Framework] 平台可以使用豐富的程式庫和可以用來建置.NET 程式庫或實作.NET 介面。 F # 也會利用許多 CLR 核心功能包括泛型、 記憶體回收、 尾端呼叫指示和基本的通用語言基礎結構 」 (CLI) 型別系統。

這篇文章會探討 F # 語言和它的實作,CLR 的頂端的核心概念。

在 F # 的快速查詢

let’s 開始以簡短的外觀,在核心語言功能,在 F # 中的數字。 詳細上任何的這些功能和許多其他有趣概念在 F # 語言中的請參閱透過在 fsharp.net F # 開發人員中心,可以使用文件]。

F # 的最基本的功能是讓的名稱所繫結值的關鍵字。 讓可用來繫結資料和函式的值及最上層和本機繫結:

let data = 12
 
let f x = 
    let sum = x + 1 
    let g y = sum + y*y 
    g x

F # 提供幾個核心資料型別,並使用 [包括清單的結構化資料的語言語法輸入的選擇性值,並有序元組:

let list1 = ["Bob"; "Jom"]

let option1 = Some("Bob")
let option2 = None

let tuple1 = (1, "one", '1')

這些片段的結構化的資料和其他人,可對符合使用 F # 模式比對運算式。 模式比對,類似於 C 類似的語言中使用參數的陳述式,但提供更豐富的方式,來比對,並要使用規則運算式的模式比對字串的方式類似有點擷取出的相符的運算式部分:

let person = Some ("Bob", 32)

match person with
| Some(name,age) -> printfn "We got %s, age %d" name age
| None -> printfn "Nope, got nobody"

F # 會運用.NET Framework 程式庫,如從豐富的各種資料來源存取資料這類的許多工作。 可以從 F # 用於其他.NET 語言相同的方式來使用.NET 程式庫:

let http url = 
    let req = WebRequest.Create(new Uri(url))
    let resp = req.GetResponse()
    let stream = resp.GetResponseStream()
    let reader = new StreamReader(stream)
    reader.ReadToEnd()

F # 也是物件導向的語言和任何.NET 類別或結構類似於 C# 或 Visual Basic,可以定義:

type Point2D(x,y) = 
    member this.X = x
    member this.Y = y
    member this.Magnitude = 
        x*x + y*y
    member this.Translate(dx, dy) = 
        new Point2D(x + dx, y + dy)

在就另外 F # 支援兩個特殊種型別:記錄和 discriminated 的等位。 記錄提供簡單的具名的欄位與資料值表示和 discriminated 等位所表達的方式來表示可以有許多不同種類的含有不同的相關資料,在每個種類中的值的型別:

type Person = 
    { Name : string;
      HomeTown : string;
      BirthDate : System.DateTime }

type Tree = 
    | Branch of Tree * Tree
    | Leaf of int

F # 在 CLR 上

F # 會在許多方面比 C# 中,其型別] 系統與更高層級的語言語法和語言建構被進一步遠離中繼資料和 CLR 的中繼語言 (IL)。 這會有幾個有趣的含意。 最重要的是它表示 F # 開發人員通常可以解決問題並思考他們在較高的層級的程式、 靠近手邊的問題的網域。 但這也表示 F # 編譯器不會對應到 CLR 的 F # 程式碼中的多個工作,而且比較不直接對應。

C# 1.0 編譯器和 CLR 所開發在此同時,兩者的功能密切。 CLR 型別系統中,並在 CIL,幾乎所有的 C# 1.0 語言建構會有非常直接的表示。 這變得較不為 True,在稍後的 C# 版本中為 C# 語言發展速度較快,比 CLR 本身。 iterators 和匿名方法是基本 didn’t 有直接的 CLR 對等用法的 C# 2.0 語言功能。 在 C# 3.0 中的查詢運算式和匿名型別後面這個趨勢。

F # 會這一步進一步。 許多語言建構 don’t 有直接的 IL 相等,所以功能 (如模式比對運算式取得編譯成一組豐富的 IL 指示用來完成有效率地符合的模式。 F # 如記錄和等位型別自動產生許多所需的成員。

但是,請注意我討論目前的 F # 編譯器所使用的編譯技術。 許多這些實作詳細資料不會直接顯示給 F # 程式開發人員,並可修改在未來版本的 F # 編譯器進行效能最佳化,或啟用新的功能。

依預設不變

在 F # 中基本的讓繫結是類似於 C# 中的 var 以外的一項很重要的差異:您 can’t 變更可讓繫結名稱之後的值。 也就是值是不變的 F # 中的預設:

let x = 5
x <- 6 // error: This value is not mutable

不變性有大的好處,來進行同步,因為不需要擔心鎖定使用不變的狀態時,它可以安全地從多個執行緒存取。 不變性也會減少元件之間的結合性。 來影響另一個元件的唯一方法,就是讓元件以明確的呼叫。

變動可以選擇在 F # 中,並會經常使用的時呼叫其他的.NET 程式庫或最佳化特定的程式碼路徑:

let mutable y = 5
y <- 6

同樣地,在 F # 中的型別都是依預設不變:

let bob = { Name = "Bob"; 
            HomeTown = "Seattle" }
// error: This field is not mutable
bob.HomeTown <- "New York" 

let bobJr = { bob with HomeTown = "Seattle" }

在此的範例沒有可用的變化時它 ’s 常用來製作新的複本,從舊的一個變更一或多個欄位時改用複製和更新。 雖然會建立一個新的物件,它會與原始共用許多棋子。 在此的範例只是單一的字串 — 「 王俊元 」 — 需要。 這個共用是效能的不變性很重要的一部分。

共用可以也出現在 F # 集合中。 就例如 F # 清單型別是可以共用與其他清單的尾端的連結清單的資料結構:

let list1 = [1;2;3]
let list2 = 0 :: list1
let list3 = List.tail list1

因為複製-和-更新和共用與不變的物件的程式設計中固有的效能設定檔,這些程式通常是從一般的命令式程式很大的差異。

CLR 會播放一大的角色。 不變的程式設計方面,會建立一個暫時性物件轉換資料,而不是就地變更它的結果。 CLR 記憶體回收行程 (GC) 處理與這些。 短暫的小物件是以梯次標記-和-跨越由 CLR GC 因為相對很便宜。

函式

F # 是功能性語言和函式不就 surprisingly 扮演相當重要的角色,在語言]。 函式是 F # 型別系統的最高級的一部份。 就例如型別 「 char-> int 」 代表 F # 函數取得字元,並傳回整數的數目

雖然類似.NET 委派,F # 函式就會有兩個重要的差異。 第一次,它們 ’re 不名義上。 任何字元,並傳回 int 的函式是型別的 「 char-> int 」,而多個不同名稱的委派可能用來表示函式的這個的簽章,而且不能互換。

第二個,F # 函式被設計來有效地支援部分或完整的應用程式。 因此導致接受其餘參數的新函式參數的子集合指定多個參數的函式時,就會是部分的應用程式。

let add x y = x + y

let add3a = add 3
let add3b y = add 3 y
let add3c = fun y -> add 3 y

所有頂級 F # 函式值都是執行個體的型別 FSharpFunc <,> F # 執行階段庫,FSharp.Core.dll 中所定義。 使用 [從 C# 的 F # 程式庫時這是會有所有 F # 函式視為參數或值從方法傳回的型別。 這個類別如以下所示外觀大致上,(如果您已定義在 C# 中):

public abstract class FSharpFunc<T, TResult> {
    public abstract TResult Invoke(T arg);
}

所有 F # 函式基本上採用單一引數,並產生一個結果,請特別注意。 這會擷取部分的應用程式的概念,與多個參數的 F # 函式實際上會像型別的執行個體:

FSharpFunc<int, FSharpFunc<char, bool>>

也就是一個 int,並傳回本身的另一個函數的函式會採用一個字元,並傳回一個 bool。 完整的應用程式的常見的情況由快速藉由使用 F # 核心程式庫中的一組協助程式型別。

當使用 Lambda 運算式來建立的 F # 函式值 (有趣關鍵字),或結果的另一個函式 (如同在前面所示 add3a 的情況下) 的部分應用程式,F # 編譯器產生終止類別:

internal class Add3Closure : FSharpFunc<int, int> {
    public override int Invoke(int arg) {
        return arg + 3;
    }
}

這些錯誤是類似於其 Lambda 運算式建構函式的 C# 和 Visual Basic 編譯器所建立的錯誤。 錯誤是其中一個最常見編譯器產生建構.NET 平台上,並沒有直接的 CLR 層級支援。 有的話) 中幾乎所有.NET 程式設計語言,並使用特別嚴重的 F #。

函式物件是公用的在 F # 中,因此 F # 編譯器會使用許多的最佳化技術來避免需要配置這些錯誤。 做為.NET 方法可能的話,使用內嵌、 Lambda 提升並直接表示,內部 F # 編譯器所產生的程式碼通常外觀比此處所描述的稍有不同。

型別推斷和泛型

一個值得注意的功能的所有程式碼範例為止是任何型別附註的缺乏。 雖然 F # 是靜態型別程式設計語言,明確型別附註則通常不會需要,因為 F # 會大量使用型別推斷。

型別推斷會熟悉 C# 和 Visual Basic 開發人員用於在這個 C# 3.0 程式碼中的本機變數:

var name = "John";

在 F # 中的可讓的關鍵字類似,但在 F # 中的型別推斷會大幅進一步,也套用到欄位、 參數和傳回型別。 在以下的範例將兩個欄位中 x 和 y 推斷具有預設值的型別 int 的 + 和 * 上這些值在型別定義的主體中使用的運算子。 翻譯方法讓型別推斷 「 翻譯:int * int-> Point2D 」:

type Point2D(x,y) = 
    member this.X = x
    member this.Y = y
    member this.Magnitude = 
        x*x + y*y
    member this.Translate(dx, dy) = 
        new Point2D(x + dx, y + dy)

就說型別附註可用時需要或想要告訴 F # 編譯器哪一種類型真的預期的特定值、 欄位或參數。 此資訊將會用於型別推斷。 比方就說,您可以變更使用浮點數,而不是藉由新增只是幾個型別附註的 int Point2D 的定義:

type Point2D(x : float,y : float) = 
    member this.X = x
    member this.Y = y
    member this.Magnitude = 
        x*x + y*y
    member this.Translate(dx, dy) = 
        new Point2D(x + dx, y + dy)

其中一個重要的型別推斷的結果是不受限於特定類型的函式會自動歸納起來是泛用的函式。 讓您的程式碼會成為泛用的盡可能不需要先明確地指定泛用的型別。 這會使播放基本的角色在 F # 中的泛型。 使用 F # 的功能性程式設計的撰寫樣式也鼓勵小可重複使用的部分功能,可大幅受益的泛用的盡可能。 能夠撰寫泛用的函式,而複雜的型別附註不是 F # 的重要功能。

就例如下列的對應函式會教值的清單,並藉由將其引數的函式 f 套用到每個項目中產生新的清單:

let rec map f values = 
    match values with
    | [] -> []
    | x :: rest -> (f x) :: (map f rest)

請注意有需要沒有型別附註,但型別推斷對應為 「 對應:(‘a-> ‘b)-> 清單 < ’ 一個 >-> 清單 < ’ b > 」。 F # 就可以推斷的模式比對,使用從及參數 f 當成函式有特定圖形的兩個參數的型別,但不是完全修正。 因此 F # 使得時仍然有型別所需的實作泛用的盡可能函式。 請注意在 F # 中的泛用參數會指出使用前置 ‘ 識別這些語法的其他名稱的字元。

Don Syme,F # 的設計工具先前前置重疊時間研究員和實作的.NET Framework 2.0 的泛型的開發人員。 像 F # 語言的概念嚴重,取決於的執行階段中的泛型和 Syme ’s 這樣做是 F # 來自部分想要真的利用此 CLR 功能的興趣。 F # 運用.NET 泛型嚴重,F # 編譯器本身的實作,就例如有多個 9,000 泛型型別參數。

最後,型別推斷是只是編譯時期功能不過,和 F # 程式碼的每一份在 CLR 中繼資料中取得編碼的推斷型別,F # 組件。

機尾呼叫

不變性和功能的程式設計通常鼓勵使用遞迴,作為在 F # 中的計算工具。 就例如在受到的 F # 清單,而且清單中值的平方和收集使用簡單的遞迴的 F # 程式碼片段:

let rec sumOfSquares nums =
    match nums with
    | [] -> 0
    | n :: rest -> (n*n) + sumOfSquares rest

雖然遞迴通常是方便的它可以在呼叫堆疊上使用很多空間因為每個反覆項目中新增新的堆疊框架。 夠大的輸入這甚至會造成堆疊溢位例外狀況。 若要避免此堆疊成長,遞迴程式碼可以寫入機尾-遞迴,表示遞迴呼叫永遠最後一個函式會傳回之前完成的項目:

let rec sumOfSquaresAcc nums acc = 
    match nums with 
    | [] -> acc
    | n :: rest -> sumOfSquaresAcc rest (acc + n*n)

F # 編譯器實作尾端遞迴函式使用兩種技術,以確保不會增加堆疊的目標。 為相同的尾端直接呼叫函式被定義如 sumOfSquaresAcc,來呼叫 F # 編譯器會自動將遞迴呼叫轉換一段迴圈] 中因此避免在就所有進行的任何呼叫,並產生與命令式相同的函式的實作非常類似的程式碼。

機尾遞迴不一定越簡單越這,不過,可以改可以多個結果互相遞迴函式。 在這種情況下 F # 編譯器會依賴尾端呼叫 CLR 原生支援。

CLR 會有專為協助尾端遞迴的 IL 指令:機尾。 IL 的前置詞。 機尾。 指示會告訴 CLR 它可以捨棄之前要進行關聯的呼叫的呼叫端 ’s 方法狀態。 這表示堆疊將不會增長時採取這個呼叫。 這也表示,至少在原則,或許可以進行有效率地使用只是一個跳躍指令呼叫 JIT 的。 這是很有用的 F #,並確保該尾端遞迴是安全 
almost 在所有情況下:

IL_0009:  tail.
IL_000b:  call    bool Program/SixThirtyEight::odd(int32)
IL_0010:  ret

在 CLR 4.0,幾個重要的改進已進行到尾端呼叫的處理方式。先前已實作 JIT 的 x64 機尾呼叫極高的效率,但可能不會套用到所有的技術的使用情況下的位置尾端。出現的指令。這表示已順利在 x86 平台會因堆疊溢位在 x64 的平台執行某些 F # 碼。在 CLR 4.0,JIT 延伸它有效率的實作的尾端的 x64 呼叫到更多的情況下,並也會實作以確保它們是在 x86 上隨時採用尾端呼叫所需的高負荷機制 JIT。

在 CLR 程式碼產生部落格 ( blogs.msdn.com/clrcodegeneration/archive/2009/05/11/tail-call-improvements-in-net-framework-4.aspx ) 上使用的 CLR 4.0 項改良的尾端呼叫詳細的帳戶。

F # 互動式

F # 互動式是命令列工具,以互動方式執行 F # 程式碼的 Visual Studio 工具視窗 (請參閱 的 圖 1)。這個工具讓您更容易試驗資料、 探索 API,並測試使用 F # 的應用程式邏輯。F # 互動式進行可能由 CLR 這些事件處理常式的 API。這個 API 可讓程式在執行階段產生新的型別和成員,並動態地呼叫進入這個新的程式碼。F # 互動式使用 F # 編譯器編譯的程式碼使用者輸入在的提示字元,然後使用這些事件處理常式產生型別]、 [函式] 和 [寫入磁碟中的組件的成員。


圖 1 的 F # 互動式中執行的程式碼

其中一個金鑰的結果,這種方法,是執行使用者程式碼完整編譯和完全 JITed] 中這兩個這些步驟執行的而不是被解譯的版本的 F # 中包括所有有用的最佳化。這使 [F # 互動式嘗試新的解決問題的方法,和以互動方式瀏覽大型資料集的極佳、 高效能的環境。

有序元組

在 F # 中的 Tuple 提供簡單的方法,用來封裝資料並傳給它周圍為一個的單位不需要定義新的自訂型別或傳回多個值使用 out 參數,例如複雜的參數配置。

let printPersonData (name, age) = 
    printfn "%s is %d years old" name age

let bob = ("Bob", 34)

printPersonData bob

    
let divMod n m = 
    n / m, n % m

let d,m = divMod 10 3

有序元組都是簡單的型別,但在 F # 中有一些重要的屬性。 最明顯的地他們 ’re 不變。 一旦建構,在有序元組元素不能修改。 這可讓安全地被視為只是其元素的組合的 Tuple。 它也可以讓 Tuple 的另一項重要功能:結構相等。 有序元組和其他 F # 型別清單、 選項,和使用者定義的記錄和等位會比較相等藉由比較其項目。

在 [.NET] Framework 4 Tuple 現在是在基底類別程式庫中定義的核心資料類型。 當.NET Framework 4 的目標 F # 使用 System.Tuple 型別來表示這些值。 mscorlib 中如果不支援這個核心型別方法 F # 使用者可以輕易地共用 Tuple 與 C# 的 API,反之亦然。

雖然 Tuple 是在概念上簡單型別,有許多有趣的設計決策參與建置 System.Tuple 型別。 Matt Ellis 設計程序的有序元組詳細說明在最近的 CLR Out 內部資料行 ( msdn.microsoft.com/magazine/dd942829 )。

最佳化

因為 F # 會轉譯,較不直接到 CLR 指示,有 ’s 更多的空間來進行的而不是只依賴 CLR JIT 編譯器,F # 編譯器最佳化。 F # 編譯器會善用這和發行模式比 C# 和 Visual Basic 編譯器中實作更重要的最佳化。

一個簡單的範例是中間的有序元組抵銷。 結構的資料經常使用 Tuple,而正在處理。 它 ’s 常見的建立,並接著在單一函式主體內毀滅的 Tuple。 當發生這種情況有 ’s 不必要的有序元組物件配置。 由於 F # 編譯器知道建立和 deconstructing Tuple can’t 有任何重要的副作用,它會嘗試避免配置中繼的有序元組。

在此的範例有序元組的物件必須配置因為它使用只被毀滅模式比對運算式中:

let getValueIfBothAreSame x y = 
    match (x,y) with
    | (Some a, Some b) when a = b -> Some a
    |_ -> None

測量的單位

就像公尺] 和 [秒數] 的度量單位常用於科學、 工程和模擬,和基本上是使用不同的數字數量的型別系統。 在 F # 中度量單位是帶到語言 ’s 型別系統直接使數量可以使用他們的單位會加註。 這些單位所執行的運算,透過和單位不相符時,會報告錯誤。 在下列的範例中,它 ’s 嘗試新增公斤與秒,不過便箋不 ’s 公斤除以秒發生錯誤時發生。

[<Measure>] type kg
/// Seconds
[<Measure>] type s
    
let x = 3.0<kg>
//val x : float<kg>

let y = 2.5<s>
// val y : float<s>

let z = x / y
//val z : float<kg/s>

let w = x + y
// Error: "The unit of measure 's' 
// does not match the unit of measure 'kg'"

測量的單位會變得非常輕量的加入,多虧了 F # 型別推斷。使用者提供的單位註釋需要接受來自外部來源的資料時,出現只在常值,並使用 [型別推斷。型別推斷會接著傳播這些透過的程式,然後檢查所有的計算會被正確地根據完成所用的單位。

雖然 F # 型別系統的一部份的量值的單位被清除在編譯階段。這表示產生的.NET 組件不包含單位,相關資訊和 CLR 只將 unitized 的值視為其基礎型別,因而造成任何效能負荷。這是相較於在執行階段是完整可用的.NET 泛型。

如果您在未來 CLR 已整合核心 CLR 型別系統中的測量單位,F # 能夠公開單位資訊,所以它可以看到從 other.NET 程式設計語言。

取得互動使用 F #

如您見 F # 提供表達、 功能、 物件導向和 explorative 的程式設計語言的.NET Framework。它整合至 Visual Studio 2010 — 包括即可跳直接在和 experimenting 使用該語言的 F # 互動式工具。

語言和工具運用完整的強大,CLR 的並介紹一些較高層級的概念,對應到中繼資料和 CLR 的 IL。還 F # 是最後只是另一個的.NET 語言,可以輕鬆地合併為新的或現有.NET 專案,感謝至一般型別系統和執行階段的元件。

Luke Hoban* 是程式管理員的 F # 小組在 Microsoft。移到 F # 小組之前, 他已針對 C# 編譯器程式管理員,並在 C# 3.0 和* LINQ。