F# 4.5 的新功能

F# 4.5 新增了 F# 語言的多項改善。 其中許多功能一起新增,可讓您在 F# 中撰寫有效率的程式碼,同時確保此程式碼安全無虞。 這樣做表示在使用這些建構時,將一些概念新增至語言和大量的編譯器分析。

開始使用

F# 4.5 適用於所有 .NET Core 散發套件和 Visual Studio 工具。 開始使用 F# 以深入了解。

範圍和類似 byref 的結構

.NET Core 中引進的 Span<T> 型別可讓您以強型別的方式來表示記憶體中的緩衝區,現在從 F# 4.5 開始允許在 F# 中使用。 下列範例示範如何重複使用以不同緩衝區表示法在 Span<T> 上運作的函式:

let safeSum (bytes: Span<byte>) =
    let mutable sum = 0
    for i in 0 .. bytes.Length - 1 do
        sum <- sum + int bytes[i]
    sum

// managed memory
let arrayMemory = Array.zeroCreate<byte>(100)
let arraySpan = new Span<byte>(arrayMemory)

safeSum(arraySpan) |> printfn "res = %d"

// native memory
let nativeMemory = Marshal.AllocHGlobal(100);
let nativeSpan = new Span<byte>(nativeMemory.ToPointer(), 100)

safeSum(nativeSpan) |> printfn "res = %d"
Marshal.FreeHGlobal(nativeMemory)

// stack memory
let mem = NativePtr.stackalloc<byte>(100)
let mem2 = mem |> NativePtr.toVoidPtr
let stackSpan = Span<byte>(mem2, 100)

safeSum(stackSpan) |> printfn "res = %d"

這一點的一個重要層面是範圍和其他類似 byref 的結構具有非常嚴格的靜態分析,由編譯器執行,以您可能會發現非預期的方式限制其使用方式。 這是 F# 4.5 中引進的效能、表達性和安全性之間的基本取捨。

改寫 byrefs

在 F# 4.5 之前,F# 中的 Byrefs 對許多應用程式而言是不安全且不健全的。 F# 4.5 中已解決 byref 的健全性問題,而且也會套用針對範圍和類似 byref 的結構進行的相同靜態分析。

inref<'T> 和 outref<'T>

為了表示唯讀、唯寫和讀寫受控指標的概念,F# 4.5 引進了 inref<'T>outref<'T> 型別,分別代表唯讀指標和唯寫指標。 每個都有不同的語意。 例如,您無法寫入 inref<'T>

let f (dt: inref<DateTime>) =
    dt <- DateTime.Now // ERROR - cannot write to an inref!

根據預設,型別推斷會將受控指標推斷為 inref<'T>,與 F# 程式碼不可變的本質一致,除非某些項目已經宣告為可變動。 若要讓某個項目成為可寫入,您必須先將型別宣告為 mutable,再將其位址傳遞至操作該型別的函式或成員。 若要深入了解,請參閱 Byrefs

唯讀結構

從 F# 4.5 開始,您可以使用 IsReadOnlyAttribute 標註結構,例如:

[<IsReadOnly; Struct>]
type S(count1: int, count2: int) =
    member x.Count1 = count1
    member x.Count2 = count2

這可讓您在結構中宣告可變成員,並發出中繼資料,讓 F# 和 C# 在從元件取用時將其視為唯讀。 若要深入了解,請參閱 ReadOnly 結構

void 指標

voidptr 型別會新增至 F# 4.5,如下列函式所示:

  • NativePtr.ofVoidPtr 以將 void 指標轉換為原生 int 指標
  • NativePtr.toVoidPtr 以將原生 int 指標轉換為 void 指標

這在與使用 void 指標的原生元件交互操作時很有幫助。

match! 關鍵字

match! 關鍵字會在計算運算式內增強模式比對:

// Code that returns an asynchronous option
let checkBananaAsync (s: string) =
    async {
        if s = "banana" then
            return Some s
        else
            return None
    }

// Now you can use 'match!'
let funcWithString (s: string) =
    async {
        match! checkBananaAsync s with
        | Some bananaString -> printfn "It's banana!"
        | None -> printfn "%s" s
}

這可讓您縮短通常牽涉到混合選項 (或其他型別) 與非同步運算式等計算運算式的程式碼。 如需詳細資訊,請參閱 match!

陣列、清單和序列運算式中的寬鬆向上轉型需求

混合型別,其中某個型別可能繼承自陣列、清單和序列運算式內的另一個型別,傳統上會要求您使用 :>upcast 將任何衍生型別向上轉型為其父型別。 現在已寬鬆,如下所示:

let x0 : obj list  = [ "a" ] // ok pre-F# 4.5
let x1 : obj list  = [ "a"; "b" ] // ok pre-F# 4.5
let x2 : obj list  = [ yield "a" :> obj ] // ok pre-F# 4.5

let x3 : obj list  = [ yield "a" ] // Now ok for F# 4.5, and can replace x2

陣列和清單運算式的縮排寬鬆

在 F# 4.5 之前,您必須在當做引數傳遞至方法呼叫時,過度縮排陣列和清單運算式。 已不再是必要項目:

module NoExcessiveIndenting =
    System.Console.WriteLine(format="{0}", arg = [|
        "hello"
    |])
    System.Console.WriteLine([|
        "hello"
    |])