F# 6 の新機能

F# 6 では、F# 言語と F# インタラクティブにいくつかの機能強化が加えられています。 これは .NET 6 と一緒にリリースされます。

最新の .NET SDK は .NET のダウンロード ページでダウンロードできます。

開始

F# 6 は、すべての .NET Core ディストリビューションと Visual Studio ツールで使用できます。 詳細については、「F# の使用を開始する」を参照してください。

task {…}

F# 6 には、F# コードで .NET タスクを作成するためのネイティブ サポートが含まれています。 たとえば、.NET 互換タスクを作成する次の F# コードがあるとします。

let readFilesTask (path1, path2) =
   async {
        let! bytes1 = File.ReadAllBytesAsync(path1) |> Async.AwaitTask
        let! bytes2 = File.ReadAllBytesAsync(path2) |> Async.AwaitTask
        return Array.append bytes1 bytes2
   } |> Async.StartAsTask

F# 6 を使用すると、このコードを次のように書き換えることができます。

let readFilesTask (path1, path2) =
   task {
        let! bytes1 = File.ReadAllBytesAsync(path1)
        let! bytes2 = File.ReadAllBytesAsync(path2)
        return Array.append bytes1 bytes2
   }

F# 5 では、優れた TaskBuilder.fs および Ply ライブラリを介してタスクのサポートを利用できました。 組み込みのサポートにコードを移行するのは簡単なはずです。 しかし、いくつかの違いがあります。名前空間と型の推定は、組み込みのサポートとこれらのライブラリの間で若干異なります。また、いくつかの追加の型の注釈が必要になる場合があります。 必要に応じて、これらのコミュニティ ライブラリを明示的に参照し、各ファイルで正しい名前空間を開く場合、F# 6 でこれらを引き続き使用できます。

task {…} の使用は、async {…} を使用する場合とよく似ています。 task {…} の使用は、次のようないくつかの点が async {…} より優れています。

  • task {...} のオーバーヘッドが低下し、非同期的な作業が迅速に実行されるホット コード パスのパフォーマンスが向上する可能性があります。
  • task {…} のステップ実行とスタック トレースのデバッグが向上する。
  • タスクを予期または生成する .NET パッケージとの相互運用がより簡単である。

async {…} に慣れている場合は、次のいくつかの違いに注意してください。

  • task {…} では、最初の待機ポイントまでタスクがすぐに実行される。
  • task {…} では、キャンセル トークンが暗黙的に伝達されない。
  • task {…} では、暗黙的なキャンセル チェックが実行されない。
  • task {…} では、非同期の tailcalls がサポートされない。 これは、介在する非同期待機がない場合に、return! .. を再帰的に使用するとスタック オーバーフローが発生する可能性があることを意味します。

一般に、タスクを使用する .NET ライブラリを相互運用している場合や、非同期コード tailcalls や暗黙的なキャンセル トークンの伝達に依存していない場合は、新しいコードで async {…} よりも task {…} を使用することを検討する必要があります。 既存のコードでは、既述の async {…} の特性に確実に依存していないことをコードで確認した場合にのみ、task {…} に切り替える必要があります。

この機能は、F# RFC FS-1097 を実装したものです。

expr[idx] を使用したよりシンプルなインデックス作成構文

F# 6 では、コレクションのインデックス作成とスライスに expr[idx] 構文を使用できます。

F# 5 までの (F# 5 を含む) F# では、インデックス作成構文として expr.[idx] が使用されていました。 expr[idx] の使用の許可は、これまでの F# について学習したことで繰り返し得られたフィードバックに基づいて行われます。また、F# でのドット表記のインデックスの使用が初めて、標準的な業界の慣習とは異なり不要であると思われた場合に行われます。

既定では、expr.[idx] の使用時に警告が生成されないため、これは破壊的変更ではありません。 しかし、コードの明確化を提案する情報メッセージがいくつか生成されます。 必要に応じて、詳細な情報メッセージもアクティブにすることができます。 たとえば、オプションの情報警告 (/warnon:3566) をアクティブにし、expr.[idx] 表記の使用の報告を開始することができます。 詳細については、インデクサー表記に関するページを参照してください。

新しいコードでは、インデックス作成構文として expr[idx] を体系的に使用することをお勧めします。

この機能は、F# RFC FS-1110 を実装したものです。

部分的なアクティブ パターンの構造体表現

F# 6 では、部分的なアクティブ パターンに対してオプションの構造体表現を使用して、"アクティブ パターン" 機能が強化されています。 これにより、属性を使用して部分的なアクティブ パターンを制限し、値オプションを返すことができます。

[<return: Struct>]
let (|Int|_|) str =
   match System.Int32.TryParse(str) with
   | true, int -> ValueSome(int)
   | _ -> ValueNone

属性を使用する必要があります。 使用サイトでは、コードは変わりません。 最終的には、割り当てが減ります。

この機能は、F# RFC FS-1039 を実装したものです。

コンピュテーション式でのオーバーロードされたカスタム操作

F# 6 では、オーバーロードされたメソッドで CustomOperationAttribute を使用できます。

コンピュテーション式ビルダー content の次の使用について考えてみましょう。

let mem = new System.IO.MemoryStream("Stream"B)
let content = ContentBuilder()
let ceResult =
    content {
        body "Name"
        body (ArraySegment<_>("Email"B, 0, 5))
        body "Password"B 2 4
        body "BYTES"B
        body mem
        body "Description" "of" "content"
    }

ここでは、body カスタム操作で異なる型のさまざまな数の引数を指定します。 これは、オーバーロードを使用する次のビルダーの実装によってサポートされています。

type Content = ArraySegment<byte> list

type ContentBuilder() =
    member _.Run(c: Content) =
        let crlf = "\r\n"B
        [|for part in List.rev c do
            yield! part.Array[part.Offset..(part.Count+part.Offset-1)]
            yield! crlf |]

    member _.Yield(_) = []

    [<CustomOperation("body")>]
    member _.Body(c: Content, segment: ArraySegment<byte>) =
        segment::c

    [<CustomOperation("body")>]
    member _.Body(c: Content, bytes: byte[]) =
        ArraySegment<byte>(bytes, 0, bytes.Length)::c

    [<CustomOperation("body")>]
    member _.Body(c: Content, bytes: byte[], offset, count) =
        ArraySegment<byte>(bytes, offset, count)::c

    [<CustomOperation("body")>]
    member _.Body(c: Content, content: System.IO.Stream) =
        let mem = new System.IO.MemoryStream()
        content.CopyTo(mem)
        let bytes = mem.ToArray()
        ArraySegment<byte>(bytes, 0, bytes.Length)::c

    [<CustomOperation("body")>]
    member _.Body(c: Content, [<ParamArray>] contents: string[]) =
        List.rev [for c in contents -> let b = Text.Encoding.ASCII.GetBytes c in ArraySegment<_>(b,0,b.Length)] @ c

この機能は、F# RFC FS-1056 を実装したものです。

''as'' パターン

F# 6 では、as パターン自体の右側をパターンにできるようになりました。 これは、型テストで入力に対してより強力な型が指定された場合に重要になります。 次に例を示します。

type Pair = Pair of int * int

let analyzeObject (input: obj) =
    match input with
    | :? (int * int) as (x, y) -> printfn $"A tuple: {x}, {y}"
    | :? Pair as Pair (x, y) -> printfn $"A DU: {x}, {y}"
    | _ -> printfn "Nope"

let input = box (1, 2)

各パターン ケースでは、入力オブジェクトの型がテストされます。 as パターンの右側を詳細なパターンにできるようになり、それ自体をより強力な型のオブジェクトと一致させることができます。

この機能は、F# RFC FS-1105 を実装したものです。

インデント構文の改訂

F# 6 により、インデント対応構文の使用時のさまざまな不整合と制限が解消されます。 RFC FS-1108 に関するページを参照してください。 これにより、F# 4.0 以降の F# の使用時に生じていた 10 個の重要な問題が解決します。

たとえば、F# 5 では、次のコードを使用できました。

let c = (
    printfn "aaaa"
    printfn "bbbb"
)

しかし、次のコードは使用できませんでした (警告が生成されました)。

let c = [
    1
    2
]

F# 6 では、両方を使用できます。 これにより、F# がよりシンプルになり、学習しやすくなります。 F# コミュニティの共同作成者である Hadrian Tang は、機能の優れた非常に重要な体系的なテストを含む、この道の先導者です。

この機能は、F# RFC FS-1108 を実装したものです。

追加の暗黙的な変換

F# 6 では、RFC FS-1093 で説明されているように、追加の "暗黙的" および "型主導" 変換のサポートをアクティブにしました。

この変更には次の 3 つの利点があります。

  1. 必要となる明示的なアップキャストが減る
  2. 必要となる明示的な整数変換が減る
  3. .NET スタイルの暗黙的な変換のファーストクラス サポートが追加される

この機能は、F# RFC FS-1093 を実装したものです。

追加の暗黙的なアップキャスト変換

F# 6 には、追加の暗黙的なアップキャスト変換が実装されています。 たとえば、F# 5 以前のバージョンでは、型の注釈が存在する場合でも、式の異なる分岐に異なるサブタイプがある関数を実装するときに、return 式でアップキャストが必要でした。 次のような F# 5 コードがあるとします。

open System
open System.IO

let findInputSource () : TextReader =
    if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
        // On Monday a TextReader
        Console.In
    else
        // On other days a StreamReader
        File.OpenText("path.txt") :> TextReader

ここで、条件付き分岐がそれぞれ TextReaderStreamReader を計算し、両方の分岐に StreamReader 型になるようにアップキャストが追加されました。 F# 6 では、これらのアップキャストが自動的に追加されるようになりました。 これは、コードがよりシンプルであることを意味します。

let findInputSource () : TextReader =
    if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
        // On Monday a TextReader
        Console.In
    else
        // On other days a StreamReader
        File.OpenText("path.txt")

必要に応じて、警告 /warnon:3388 を有効にし、「暗黙的な変換に関するオプションの警告」で説明されているように、追加の暗黙的なアップキャストが使用されるたびに警告を表示することができます。

暗黙的な整数変換

F# 6 では、両方の型がわかっている場合、32 ビット整数が 64 ビット整数に拡大されます。 たとえば、次のような一般的な API シェイプがあるとします。

type Tensor(…) =
    static member Create(sizes: seq<int64>) = Tensor(…)

F# 5 では、int64 の整数リテラルを使用する必要があります。

Tensor.Create([100L; 10L; 10L])

または

Tensor.Create([int64 100; int64 10; int64 10])

F# 6 では、型の推定時にソースとターゲットの両方の型がわかっている場合、int32int64 に、int32nativeint に、int32double に自動的に拡大されます。 したがって、前の例のような場合は、int32 リテラルを使用できます。

Tensor.Create([100; 10; 10])

この変更にかかわらず、F# では、ほとんどの場合、数値型の明示的な拡大が引き続き使用されます。 たとえば、暗黙的な拡大は、int8int16 などの他の数値型、または float32 から float64 の場合、あるいはソースとターゲットのいずれかの型が不明である場合は適用されません。 必要に応じて、警告 /warnon:3389 を有効にし、「暗黙的な変換に関するオプションの警告」で説明されているように、暗黙的な数値の拡大が使用されるたびに警告を表示することもできます。

.NET スタイルの暗黙的な変換のファーストクラス サポート

F# 6 では、.NET の "op_Implicit" 変換が、メソッドを呼び出すときに F# コードで自動的に適用されます。 たとえば、F# 5 では、XML 用 .NET API を操作するときに XName.op_Implicit を使用する必要がありました。

open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")

F# 6 では、ソース式とターゲット型に対して型を使用できる場合、引数式に対して op_Implicit 変換が自動的に適用されます。

open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")

必要に応じて、警告 /warnon:3395 を有効にし、「暗黙的な変換に関するオプションの警告」で説明されているように、メソッド引数で op_Implicit 変換の拡大が使用されるたびに、警告を表示することができます。

Note

F# 6 の最初のリリースでは、この警告番号は /warnon:3390 でした。 競合が発生したため、後で警告番号が /warnon:3395 に更新されました。

暗黙的な変換に関するオプションの警告

型指向および暗黙的な変換は型の推定とのやりとりが不十分であり、その結果、理解しにくいコードになるおそれがあります。 このため、いくつかの軽減策が存在します。これらは、確実にこの機能が F# コードで悪用されないようにするのに役立ちます。 まず、ソースとターゲットの両方の型が厳密に認識され、あいまいさや追加の型の推定が発生しないようにする必要があります。 次に、オプトイン警告をアクティブにし、既定で 1 つの警告をオンにして暗黙的な変換の使用を報告できます。

  • /warnon:3388 (追加の暗黙的なアップキャスト)
  • /warnon:3389 (暗黙的な数値の拡大)
  • /warnon:3391 (メソッド以外の引数での op_Implicit。既定ではオン)
  • /warnon:3395 (メソッド引数での op_Implicit)

チームで暗黙的な変換のすべての使用を禁止する場合は、/warnaserror:3388/warnaserror:3389/warnaserror:3391、および /warnaserror:3395 を指定することもできます。

バイナリ数値の書式設定

F# 6 では、バイナリ数値形式で使用可能な書式指定子に %B パターンが追加されています。 次のような F# コードがあるとします。

printf "%o" 123
printf "%B" 123

このコードでは、次のような出力が印刷されます。

173
1111011

この機能は、F# RFC FS-1100 を実装したものです。

use バインディングでの破棄

F# 6 では、use バインディングで _ を使用することができます。例を以下に示します。

let doSomething () =
    use _ = System.IO.File.OpenText("input.txt")
    printfn "reading the file"

この機能は、F# RFC FS-1102 を実装したものです。

InlineIfLambda

F# コンパイラには、コードのインライン化を実行するオプティマイザーが含まれています。 F# 6 では、引数がラムダ関数であると判断された場合、その引数自体を呼び出しサイトで常にインライン化する必要があることを、必要に応じてコードで示すことができる新しい宣言機能を追加しました。

たとえば、配列を走査するための次の iterateTwice 関数があるとします。

let inline iterateTwice ([<InlineIfLambda>] action) (array: 'T[]) =
    for j = 0 to array.Length-1 do
        action array[j]
    for j = 0 to array.Length-1 do
        action array[j]

呼び出しサイトが次のような場合:

let arr = [| 1.. 100 |]
let mutable sum = 0
arr  |> iterateTwice (fun x ->
    sum <- sum + x)

インライン化とその他の最適化の後、コードは次のようになります。

let arr = [| 1.. 100 |]
let mutable sum = 0
for j = 0 to arr.Length-1 do
    sum <- sum + arr[j]
for j = 0 to arr.Length-1 do
    sum <- sum + arr[j]

以前のバージョンの F# とは異なり、この最適化は、関連するラムダ式のサイズに関係なく適用されます。 この機能を使用して、ループのアンロールや同様の変換をより確実に実装することもできます。

オプトイン警告 (/warnon:3517。既定ではオフ) をオンにすると、呼び出しサイトで InlineIfLambda 引数がラムダ式にバインドされていないコード内の場所を示すことができます。 通常の状況では、この警告を有効にしないでください。 しかし、特定の種類の高パフォーマンス プログラミングでは、すべてのコードが確実にインライン化およびフラット化されるようにするのに役立つ場合があります。

この機能は、F# RFC FS-1098 を実装したものです。

再開可能なコード

F# 6 の task {…} サポートは、''再開可能なコード'' (RFC FS-1087) と呼ばれる基盤上に構築されています。 再開可能なコードは、さまざまな種類の高パフォーマンスの非同期および生成状態のマシンを構築するために使用できる技術的な機能です。

追加のコレクション関数

Fsharp.Core 6.0.0 では、コア コレクション関数に 5 つの新しい操作が追加されています。 次の関数が該当します。

  • List/Array/Seq.insertAt
  • List/Array/Seq.removeAt
  • List/Array/Seq.updateAt
  • List/Array/Seq.insertManyAt
  • List/Array/Seq.removeManyAt

これらすべての関数では、対応するコレクション型またはシーケンスに対してコピーと更新操作が実行されます。 この種の操作は、"機能更新" の形式です。 これらの関数の使用例については、対応するドキュメント (たとえば、List.insertAt) を参照してください。

例として、Elmish スタイルで記述された単純な "Todo List" アプリケーションのモデル、メッセージ、および更新ロジックについて考えてみましょう。 ここでは、ユーザーがアプリケーションを操作して、メッセージを生成し、update 関数でこれらのメッセージが処理されて、新しいモデルが生成されます。

type Model =
    { ToDo: string list }

type Message =
    | InsertToDo of index: int * what: string
    | RemoveToDo of index: int
    | LoadedToDos of index: int * what: string list

let update (model: Model) (message: Message) =
    match message with
    | InsertToDo (index, what) ->
        { model with ToDo = model.ToDo |> List.insertAt index what }
    | RemoveToDo index ->
        { model with ToDo = model.ToDo |> List.removeAt index }
    | LoadedToDos (index, what) ->
        { model with ToDo = model.ToDo |> List.insertManyAt index what }

これらの新しい関数を使用すると、ロジックは明確で単純なものになり、不変データのみに依存するようになります。

この機能は、F# RFC FS-1113 を実装したものです。

マップに Key と Value がある

FSharp.Core 6.0.0 では、Map 型で Key および Value プロパティがサポートされるようになりました。 これらのプロパティでは基になるコレクションはコピーされません。

この機能については、F# RFC FS-1113 に記載されています。

NativePtr の追加の組み込み

FSharp.Core 6.0.0 には、次のように NativePtr モジュールに新しい組み込みが追加されています。

  • NativePtr.nullPtr
  • NativePtr.isNullPtr
  • NativePtr.initBlock
  • NativePtr.clear
  • NativePtr.copy
  • NativePtr.copyBlock
  • NativePtr.ofILSigPtr
  • NativePtr.toILSigPtr

NativePtr の他の関数と同様に、これらの関数はインライン化されており、/nowarn:9 が使用されていない限り、これらを使用すると警告が生成されます。 これらの関数の使用は、アンマネージ型に限定されます。

この機能については、F# RFC FS-1109 に記載されています。

追加の数値型と単位注釈

F# 6 では、次の型または型の省略形のエイリアスで、測定単位の注釈がサポートされるようになりました。 新たに追加されたものは太字で表示されています。

F# のエイリアス CLR 型
float32/single System.Single
float/double System.Double
decimal System.Decimal
sbyte/int8 System.SByte
int16 System.Int16
int/int32 System.Int32
int64 System.Int64
byte/uint8 System.Byte
uint16 System.UInt16
uint/uint32 System.UInt32
uint64 System.UIn64
nativeint System.IntPtr
unativeint System.UIntPtr

たとえば、次のように符号なし整数に注釈を付けることができます。

[<Measure>]
type days

let better_age = 3u<days>

この機能については、F# RFC FS-1091 に記載されています。

ほとんど使用されないシンボリック演算子の情報警告

F# 6 には、F# 6 以降での :=!incr、および decr の使用を非正規化するソフト ガイダンスが追加されています。 これらの演算子と関数を使用すると、コードを Value プロパティの明示的な使用に置き換えるように求める情報メッセージが生成されます。

F# プログラミングでは、ヒープに割り当てられた変更可能なレジスタで参照セルを使用できます。 これらは便利な場合がありますが、代わりに let mutable を使用できるため、最新の F# コーディングではほとんど必要ありません。 F# コア ライブラリには、2 つの演算子 :=! および 2 つの関数 incrdecr が含まれており、特に参照呼び出しに関連しています。 これらの演算子がある場合、参照セルが必要以上に F# プログラミングの中心となり、すべての F# プログラマがこれらの演算子を認識する必要があります。 さらに、! 演算子は、C# やその他の言語の not 演算と混同されやすくなることがあり、コードを翻訳する際には軽微なバグの原因となる可能性があります。

この変更の根拠は、F# プログラマが認識する必要のある演算子の数を減らし、初心者向けに F# を簡略化することです。

たとえば、次のような F# 5 コードがあるとします。

let r = ref 0

let doSomething() =
    printfn "doing something"
    r := !r + 1

まず、最新の F# コーディングでは、通常は代わりに let mutable を使用できるため、参照セルはほとんど必要ありません。

let mutable r = 0

let doSomething() =
    printfn "doing something"
    r <- r + 1

参照セルを使用する場合、F# 6 では、最終行を r.Value <- r.Value + 1 に変更するように求める情報警告が生成され、参照セルの適切な使用に関する詳細なガイダンスにリンクされます。

let r = ref 0

let doSomething() =
    printfn "doing something"
    r.Value <- r.Value + 1

これらのメッセージは警告ではありません。これらは、IDE とコンパイラの出力に表示される "情報メッセージ" です。 F# では下位互換性が維持されます。

この機能は、F# RFC FS-1111 を実装したものです。

F# ツール: Visual Studio でスクリプトを作成する場合の既定値 .NET 6

Visual Studio で F# スクリプト (.fsx) を開くか実行する場合、既定では、64 ビットの実行で .NET 6 を使用してスクリプトが分析され、実行されます。 この機能は Visual Studio 2019 の以降のリリースではプレビュー段階でしたが、既定で有効化されるようになりました。

.NET Framework スクリプトを有効にするには、 [ツール]>[オプション]>[F# ツール]>[F# インタラクティブ] を選択します。 [.NET Core スクリプトを使用する][false] に設定してから、F# インタラクティブ ウィンドウを再起動します。 この設定は、スクリプトの編集とスクリプトの実行の両方に影響します。 .NET Framework スクリプトに対して 32 ビットの実行を有効にするには、 [64 ビット F# インタラクティブ][false] に設定します。 .NET Core スクリプトには 32 ビットのオプションはありません。

F# ツール: SDK バージョンの F# スクリプトをピン留めする

.NET SDK 設定の global.json ファイルが格納されているディレクトリで dotnet fsi 使用してスクリプトを実行する場合、そのスクリプトの実行と編集に、一覧表示されているバージョンの .NET SDK が使用されます。 この機能は、F# 5 以降のバージョンで使用できます。

たとえば、.NET SDK バージョン ポリシーを指定する次の global.json ファイルを含むディレクトリにスクリプトがあるとします。

{
  "sdk": {
    "version": "5.0.200",
    "rollForward": "minor"
  }
}

ここで、このディレクトリから dotnet fsi を使用して、そのスクリプトを実行した場合、SDK のバージョンが優先されます。 これは、スクリプトのコンパイル、分析、実行に使用される SDK を "ロック ダウン" できる強力な機能です。

Visual Studio およびその他の IDE でスクリプトを開いて編集する場合、スクリプトを分析して確認する際にツールではこの設定が優先されます。 SDK が見つからない場合は、開発マシンにインストールする必要があります。

Linux およびその他の Unix システムでは、これをシバンと組み合わせて、スクリプトを直接実行するための言語バージョンを指定することもできます。 script.fsx のシンプルなシバンは次のとおりです。

#!/usr/bin/env -S dotnet fsi

printfn "Hello, world"

これで、script.fsx を使用してスクリプトを直接実行できるようになりました。 このように、既定以外の特定の言語バージョンとこれを組み合わせることができます。

#!/usr/bin/env -S dotnet fsi --langversion:5.0

Note

この設定は、最新の言語バージョンを想定してスクリプトを分析する編集ツールでは無視されます。

レガシ機能の削除

F# 2.0 以降では、非推奨のレガシ機能によっては、警告が長くなることがあります。 /langversion:5.0 を明示的に使用しない限り、F# 6 でこれらの機能を使用するとエラーが発生します。 エラーが発生する機能は次のとおりです。

  • 後置型名を使用する複数のジェネリック パラメーター (たとえば、(int, int) Dictionary)。 これは、F# 6 ではエラーになります。 代わりに、標準の構文 Dictionary<int,int> を使用する必要があります。
  • #indent "off"。 これはエラーになります。
  • x.(expr)。 これはエラーになります。
  • module M = struct … end 。 これはエラーになります。
  • 入力 *.ml および *.mli の使用。 これはエラーになります。
  • (*IF-CAML*) または (*IF-OCAML*) の使用。 これはエラーになります。
  • 挿入演算子としての landlorlxorlsllsr、または asr の使用。 これらは F# の挿入キーワードです。それらは OCaml の挿入キーワードでしたが、Fsharp.Core では定義されていないためです。 これらのキーワードを使用すると、警告が生成されるようになります。

これは、F# RFC FS-1114 を実装したものです。