F# 11 の新機能

F# 11へようこそ。 このリリースにより、ドメイン モデルを明確に区別したまま維持し、汎用アルゴリズムを再利用し、非同期処理を調整し、コンパイラのメモリ使用量を削減できます。

これらの変更の背後にある提案、実装、レビュー、テストについて F# コミュニティに感謝します。 共同作成者のリンクは、以下の特集に付いています。

Note

この例には、次に示す機能を備えた F# 11 コンパイラと FSharp.Core 11 が必要です。 .NET 11.0.100 SDK では、F# 11.2 の機能には <LangVersion>preview</LangVersion> が必要です。言語設定と計画された SDK のサポートを参照してください。 runtime-async の例には、追加のランタイム要件があります。以前の .NET 11 SDK だけでは、すべての変更が含まれていない可能性があります。

レコードスプレッド: 反復的なマッピングのない明確に区別されたモデル

ストレージ モデルとアプリケーション モデルを区別するために、レコード スプレッドは宣言を繰り返さずに共有フィールドを再利用します。 ここでは、モデルは顧客の詳細を共有しますが、さまざまな識別子の種類を使用します。

open System

type CustomerId = CustomerId of Guid

type CustomerDetails = { Name: string; PreferredLanguage: string }

type CustomerRow =
    { ...CustomerDetails
      Id: string
      InternalNote: string }

type Customer =
    { ...CustomerDetails
      Id: CustomerId }

...CustomerDetails はフィールド宣言を追加します。 境界では、...row は変換が識別子を解析する間、値をコピーします。

let toCustomer (row: CustomerRow) =
    match Guid.TryParse row.Id with
    | true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
    | false, _ -> Error "Invalid customer identifier."

送信先は共有の詳細を保持し、Idを置き換え、InternalNoteを除外します。 Guid.TryParseは、展開ではなく、識別子を検証します。入力が正しくない場合はErrorになります。

これはアプリケーション層マッピングの問題に対処します。 値のスプレッドでは、名前付きレコードと匿名レコードも組み合わせられます。型スプレッドでは、名前付きレコードのみが作成されます。 後のフィールドが優先され、ソースは順番に 1 回実行されます。 構成例と共有進化のリスクについては、レコードのスプレッド演算子を参照してください。

レコードスプレッドについては@brianrourkebollに感謝します

ジェネリック アルゴリズム、演算子、タプルの拡張メンバー

既存の型間でアルゴリズムを共有するために、F# 11.2 では 、拡張メンバーが静的に解決された型パラメーター (SRTP) 制約を満たすことができます。 たとえば、1 つの変換で、ラッパー型のないリストとオプションを処理できます。

Note

.NET 11.0.100 SDK で、拡張機能が解決される場所で <LangVersion>preview</LangVersion> を有効にし、それらのモジュールを開きます。 ライブラリの要件を参照してください。

リストとオプション間で変換を共有する

ライブラリの map がスコープ内にある場合、アプリケーションは、いずれかの表現をクリーンアップし、要素の型を変更し、変換を合成できます。

let inline cleanNames values =
    map (fun (name: string) -> name.Trim().ToUpperInvariant()) values

let batch: string list = cleanNames [ " Ada "; " grace " ]
let optional: string option = cleanNames (Some " linus ")
let missing: string option = cleanNames (None: string option)
let lengths: int list = map String.length batch
let nested: int option list = map (map String.length) [ Some "abc"; None ]

printfn "%A; %A; %A" batch optional missing
printfn "%A; %A" lengths nested
// ["ADA"; "GRACE"]; Some "LINUS"; None
// [3; 5]; [Some 3; None]

Structural.Adaptersのアダプターは、新しいコンテナーを適用するのではなく、使い慣れた操作を使用します。

type List<'T> with
    static member Map(source: 'T list, mapping: 'T -> 'U) : 'U list =
        List.map mapping source

type Option<'T> with
    static member Map(source: 'T option, mapping: 'T -> 'U) : 'U option =
        Option.map mapping source

Structural.Algorithmsの共有関数は、入力型に静的なMapメンバーを要求します。

let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
    (^Input :
        (static member Map: ^Input * ('T -> 'U) -> ^Output)
        (source, mapping))

^Input と ^Output は静的に解決される型です。 コンパイラは、各具象呼び出しで、必要な署名を持つメンバーを選択します。 拡張では、インターフェイスの実装は追加しません。 アプリケーション全体は、両方のモジュールを開きます。

演算子、コンテキスト、および出力の種類

同じメカニズムで、 タプル演算子とコンテキスト伝達 と 出力指向の構築がサポートされます。 ネイティブ タプル ヘッダーでは、F# 11.2 機能が宣言時および拡張解決の呼び出しサイトで有効になっている必要があります。

別の機能追加AllowOverloadOnReturnTypeでは、1 つのファクトリで、戻り値によって区別されるオーバーロードを宣言できます。

type Inject =
    [<AllowOverloadOnReturnType>]
    static member Pure(value: 'T) : 'T list = [value]

    [<AllowOverloadOnReturnType>]
    static member Pure(value: 'T) : 'T option = Some value

let batch: string list = Inject.Pure "ready"
let optional: string option = Inject.Pure "ready"
// ["ready"] and Some "ready"

この Core 11 属性にはプレビューは必要ありません。 結果注釈はオーバーロードを選択します。ない場合、呼び出しはあいまいなままです。 「戻り値の型で選択する」を参照してください。

@gusty による汎用プログラミング サポートの拡張に感謝します。

.NET統合と開発エクスペリエンス

直接デリゲートはハンドラーの実際のコントラクトを保持します

ハンドラー名とバインド属性をフレームワークに公開するために、F# 11 では、ラッパーではなく、互換性のある名前付き関数を直接ターゲットにすることができます。 ASP.NET Core Minimal APIs は、次にルート、サービス、およびヘッダー コントラクトを読み取ることができます。

open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.DependencyInjection

type ShipmentStore() =
    member _.Status trackingId =
        if trackingId = "SHP-1042" then "Dispatched" else "Unknown"

let status
    (trackingId: string)
    ([<FromServices>] store: ShipmentStore)
    ([<FromHeader(Name = "X-Customer")>] customer: string) =
    $"{customer}: {trackingId} is {store.Status trackingId}"

let handler = Func<string, ShipmentStore, string, string>(status)

実行可能なプロジェクトと構築/互換性の規則を参照してください。 キャプチャと引数の適応には、ラッパーが引き続き必要な場合があります。

直接代理人の @kerams に感謝します。

XML ドキュメントを再利用する

共有 API 解説を 1 回保持するには、 shared.xmlに配置します。

<docs>
  <tracking>
    <remarks>An unrecognized tracking identifier has status Unknown.</remarks>
  </tracking>
</docs>

GenerateDocumentationFile有効にすると、F# 11 は出力されるメンバー エントリに<include>を展開します。

/// <summary>Find the latest status of a shipment.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let status trackingId =
    if trackingId = "SHP-1042" then "Dispatched" else "Unknown"

/// <summary>Find the latest statuses of several shipments.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let statuses trackingIds =
    trackingIds |> List.map (fun id -> id, status id)

インクルード実装では、1 つのフラグメントから両方の説明が更新されます。 個別に、 ツール リゾルバー は <inheritdoc/>の基本説明を公開します。コンパイラはそのタグをドキュメント コンシューマーに残します。 例と解決規則については、 XML ドキュメントを参照 してください。

@T-Gro が FSharp.Core で XML ドキュメントを共有してくれたことに感謝します。

最適化されていないビルドでインライン関数をデバッグする

インライン ヘルパー引数を検査するには、最適化されていないビルドの let total にブレークポイントを設定し、 addFeeにステップ インします。 F# 11 では、次のビルドで呼び出し可能なメソッドが優先されるようになりました。

let inline addFee (fee: decimal) (amount: decimal) =
    let total = amount + fee
    total

let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42

最適化されたビルドはインライン化を保持します。 一部のコンストラクトは引き続き拡張する必要があります。 これらの制限と コントロールについては、「--always-inline」を参照してください。

非同期とタスクの調整

F# ワークフローをタスク ベースの API に接続するために、FSharp.Core 11 では、キャンセル対応ブリッジ、有界バッチ、および結果変換が追加されます。

結果コレクションだけでなく、未処理の I/O をバインドしました

読み取りのバッチが大きいと、アプリケーションが抱えるべき未処理作業よりも多くの作業が開始される可能性があります。 タスク ベースの作業がいつ開始するかを決定するには、コーディネーターには、既に開始されているタスクではなく、ファクトリーが必要です。

open System.IO
open System.Threading

let readFiles (paths: seq<string>) (ct: CancellationToken) =
    paths
    |> Seq.map (fun path ->
        fun token -> File.ReadAllTextAsync(path, token))
    |> Task.parallelLimit 4 ct

最大 4 つの読み取りが同時に実行されます。結果は入力順序を保持します。 失敗は、指定されたトークンを介して兄弟の取り消しを要求します。 これにより、1 秒あたりの要求ではなく、コンカレンシーが制限されます。 シーケンシャルおよびユニット結果のバリアントについては、 非同期関数とタスク関数 を参照してください。

取り消しと基になる例外を境界を越えて保持する

ワークフローが外部作業を開始すると、そのトークンはその作業と共に移動する必要があります。 Async.StartTaskImmediate は、実行中のワークフローのトークンをタスクファクトリに渡し、完了を待ちます。

open System.IO

let tryReadFile path =
    async {
        try
            let! contents =
                Async.StartTaskImmediate(fun ct -> File.ReadAllTextAsync(path, ct))
            return Some contents
        with :? FileNotFoundException ->
            return None
    }

1 つのタスクの失敗が、基になる FileNotFoundExceptionの形でこのハンドラーで処理されます。 既に作成されているタスクまたは値タスクの場合は、Async.Await を使用します。古い Async.AwaitTask は例外をラップする動作を保持します。

タスク ベースの呼び出し元の場合、 Task.startAsyncImmediate token workflow はリバース ブリッジを提供します。 最初の非同期中断まで、呼び出し元のスレッドで開始されます。

短い変換には対応するヘルパーを使用する

完全な計算式を使わずに単一の結果を変換するには、Microsoft.FSharp.Control 内の対応するヘルパーを使用します。 たとえば、ファイルを読み取り、その長さを取得し、通常の失敗を Errorとして返します。

open System.IO
open System.Threading

let fileLength (path: string) (ct: CancellationToken) =
    File.ReadAllTextAsync(path, ct)
    |> Task.map String.length
    |> Task.catch

Report 1を含むファイルはOk 8を提供します。見つからないファイルは、Errorを含むFileNotFoundExceptionを返します。 キャンセルは引き続き適用されます。 Async および ValueTask は、一致する map、bind、およびエラー ヘルパーを提供します。Async 関数と task 関数を参照してください。 分岐や複数の相互に依存する値には、コンピュテーション式を使用します。

ValueTask ヘルパーには、Core の netstandard2.1 または net10.0 ビルドと 1 回のみの使用が必要です。

@bartelink の async/task API への貢献に感謝します。

非同期エラー報告後の再送出

操作ログは、呼び出し元がキャッチする内容を変更したり、障害が発生した場所を非表示にしたりすることなく、情報を追加する必要があります。 計算式ハンドラーで、logger を await した後でも reraise () を使用できるようになりました。

Note

計算式の再スローは、F# 11.2 の機能です。 .NET 11.0.100 SDK で、<LangVersion>preview</LangVersion> を有効にする。

open System
open System.IO
open System.Threading.Tasks

let readReport (logFailure: exn -> Task) path =
    task {
        try
            return! File.ReadAllTextAsync(path)
        with :? IOException as error ->
            do! logFailure error
            return reraise ()
    }

ログ出力が正常に完了すると、同じ例外と元の throw-site 情報が伝達されます。 ロガーの障害は、元のものを置き換えることができます。 これにより、ランタイムでの非同期処理を使わずに 可観測性に関する非同期リクエスト に対応できます。 コンピュテーション式からの再スローを参照してください。

このサポートの @xperiandri に感謝します。

FSharp.Core は最新の .NET 上でビルドできます

以前のターゲットを削除せずに最新の API を使用するために、FSharp.Core はnetstandard2.0とnetstandard2.1と共にnet10.0を追加します。 NuGet は互換性のあるビルドを選択します。 更新されたツール パッケージにより、コンパイル済みプロジェクトと同様に、F# Interactive でのタスク ベースの非同期破棄も可能になります。 FSharp.Core ターゲット フレームワークを参照してください。

モダン Core ターゲティングと@T-Groについては、に感謝します。

ビルダー作成者のランタイム非同期

ビルダーの実装者は、コンパイラ生成のステートマシンを使うのではなく、ランタイムによってマークされたメソッドを中断および再開できるようにできます。 実験的なライブラリで定義されたビルダーは、その仕組みを計算式として提供します。

let readManifest (path: string) =
    runtimeTask {
        let! text = System.IO.File.ReadAllTextAsync path
        return text.Length
    }

Note

この実験的なビルダーは、そのインライン実装が定義または展開される箇所で使用される F# 11.2 の機能を使用しています。.NET 11.0.100 SDK では、プレビューを有効にしてください。 また、Core 11 の net10.0 ビルドと参照設定、および runtime-async サポートを備えたランタイムも必要です。 ここで検証されたランタイムと参照の組み合わせは、.NET 11 RC1です。 .NET 10 を対象にするだけでは不十分です。 Runtime async experiments を参照してください。

FSharp.Core には、このサンプルは汎用ビルダーとして含まれていません。 通常の task と async コードがそれに自動的に切り替わることはありません。また、既にコンパイル済みの通常のタスクを返す API の呼び出し元には、本質的にプレビューは必要ありません。

ランタイム非同期対応について @majocha に感謝します。

Native AOT 対応のテキストと 2 次元配列

ネイティブ AOT をサポートするために、F# 11 では、一般的なテキスト処理や配列操作で必要とされていたリフレクションベースの書式設定と動的メモリ割り当てが不要になります。

既に文字列がある場合は、文字列を書き込むためだけに "%s" フォーマットは必要ありません。 新しいコア関数printとstringは、F# のprintn変換によって値を受け取り、printnは改行を追加します。

let report (processed: int) (total: decimal) (elapsed: TimeSpan) : string =
    $"{processed} items, total {total:F2}, elapsed {elapsed.TotalMilliseconds:F0} ms"

printn (report 12 145.50m (TimeSpan.FromMilliseconds 250.0))
// 12 items, total 145.50, elapsed 250 ms

文字列補間では、より多くのケースで直接変換と String.Concat が使用されます。 {total:F2} のような .NET 形式のプレースホルダーは、インバリアント書式を使用し、値をボックス化します。一方、%A や精度指定付きの printf 形式では、引き続き printf の書式設定が必要になる場合があります。 ローカライズされた出力用の明示的なロケールを選択します。

--reflectionfreeでは、レコードとユニオンでも生成されたToString出力が得られます。 カルチャの例とサポートされているパスについては、 補間とネイティブ AOT に関するページを参照してください。

印刷関数については@bbatsovに、補間の実装については@charlesroddieに感謝します。

通常のゼロベースのグリッド操作を保持する

0 から始まるグリッドでは、単に動的コードの割り当てパスを回避するためだけに、置き換え用の Array2D モジュールは必要ありません。 更新されたコア実装では、使い慣れた初期化と変換がサポートされます。

let grid = Array2D.init 3 4 (fun row column -> row * 4 + column)
let doubled = grid |> Array2D.map (fun value -> value * 2)
let snapshot = Array2D.copy doubled

printn $"{snapshot.GetLength(0)} x {snapshot.GetLength(1)}; last = {snapshot[2, 3]}"
// 3 x 4; last = 22

Array2D.create、init、および rebase は、コア ビルド全体で 0 から始まる割り当てパスを使用します。 境界保持のmap、mapi、およびcopyでは、net10.0 ビルドで新しい .NET の割り当て API を使用できます。.NET Standard ビルドでは、フォールバックが維持されます。 ゼロベースでない配列は非推奨ではありませんが、ネイティブでの割り当てに関する制限は引き続き残ります。

割り当てパスと残りの制限については、 2 次元配列とネイティブ AOT を参照してください。 完全なアプリケーションの公開診断を確認してください。

Array2D の機能強化に対して @T-Gro に感謝します。

メモリの保持が少なく、作業の繰り返しが少なくなります

F# 11 では、コンパイラの繰り返し分析とアプリケーションでの一部のメモリ割り当てが削減されます。 これらはさまざまなワークロードに影響します。

プロジェクト間で参照先ライブラリを共有する

共有バイナリ依存関係の繰り返しコピーを回避するために、言語サービスは、共有可能なインポートされたライブラリ グラフをプロジェクト間で共有できます。

実装した PR の保持メモリ実験では、10 プロジェクトの ReSharper.FSharp ソリューションは、共有が有効になっている状態で 1,547.2 MB から 1,002.2 MB に減少しました。 これは、一般的な F# 10 から 11 への低減ではなく、そのワークロードのオフ/オン測定です。 共有バイナリ参照ではなくプロジェクト参照によって支配されるソリューションは、はるかに小さな改善しか見られないことがあります。

メタデータの読み取りとインラインの特殊化により、一時的な割り当ても削減されます。これは別の指標です。 スコープとコントロールについては、ツーリングのパフォーマンスを参照してください。

共有ライブラリのインポートについては、@auduchinok に感謝します。

既定で独立した処理を並列コンパイルする

並列コンパイル は既定で有効になり、確定的な出力とソースコードの順序に関する規則を維持したまま利用できるようになりました。 オーバーロードのキャッシュとガード付きパターンの改善により、そのほかの繰り返し処理を避けられます。 Visual Studio/FCS では、未変更ドキュメントの解析を減らし、同時チェック数を制限します。詳細については、ツールのパフォーマンスを参照してください。

コールバックと空の結果オブジェクトの割り当てによるオーバーヘッドを回避する

選択した List 関数と Array 関数で、コールバックをインライン化できるようになりました。 独自のインラインヘルパーに対しては、OptimizeClosureIfNotInlined が不透明なカリー化されたコールバックを一度だけ適応します。

let inline foldItems
    ([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
    state
    (items: 'T[]) =
    let mutable total = state
    for item in items do
        total <- folder total item
    total

その他の割り当ては残すことができます。 要件については、カリー化されたコールバックの最適化を参照してください。

結果が空であることも、処理が正常に行われた場合によくあるケースの一つです。 FSharpLint の動機となったワークロードでは、警告を検出しないルールが繰り返し組み合わされていました。 Core で、より多くの操作において共有された空の配列が再利用されるようになりました。

let checkTitle (title: string) =
    if String.IsNullOrWhiteSpace title then [|"A title is required."|]
    else Array.empty

let warnings = ["Introduction"; "Installation"] |> List.toArray |> Array.collect checkTitle
let sharedEmpty = obj.ReferenceEquals(warnings, Array.empty<string>)
// true: no new empty result array

この変更により、パイプライン内のすべての割り当てではなく、冗長な空配列オブジェクトが削除されます。 それぞれ異なる参照の同一性を持つ空の結果に依存しないでください。

インラインコレクションコールバックと@T-Groに感謝します。

より正確な API 呼び出しと日常で役立つ便利機能

生成されたパラメーターの順序が変更される可能性があるときに名前を要求する

生成された API を気付かれない引数順の変更から保護するために、作成者は名前付き引数を要求できます。

Note

適用は F# 11.2 の機能です。.NET 11.0.100 SDK では、プレビューを有効にしてください。 使用可能な System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute 定義も必要です。 完全なサンプルでは、互換性定義を欠く参照に互換性定義を提供します。

open System.Diagnostics.CodeAnalysis

type Query =
    [<RequireNamedArguments>]
    static member Execute(numerator: decimal, denominator: decimal) =
        numerator / denominator

let ratio = Query.Execute(numerator = 3m, denominator = 4m)
// 0.75M; Query.Execute(3m, 4m) is rejected.

宣言の順序を変更しても、名前付き呼び出しの結果0.75Mは保持されます。位置ベースの呼び出しは拒否されます。 生成された API ユース ケースの場合、プロバイダーは注釈を出力する必要があります。

既にラップされているジェネリック引数を認識する

既存のタスクをプレーンな結果値としてラップしないように、新しい最後の手段のオーバーロード ルールでは、ベア Task<'T>よりも 'T が優先されます。

Note

最も具体的なタイブレーカーとオーバーロード優先動作は、F# 11.2 の機能です。.NET 11.0.100 SDK でプレビューを有効にします。

open System.Threading.Tasks

let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>

結果はValueTask<Task<int>>であり、ValueTask<int>ではありません。 ルールの制限については、「 オーバーロードの選択」 を参照してください。

古いエントリ ポイントを削除せずに新しいオーバーロードを優先する

ライブラリは、呼び出し元のソース位置を含むオーバーロードを追加するときに、既存のバイナリの古い診断メソッドを保持する場合があります。 OverloadResolutionPriorityAttribute では、新たにコンパイルされる呼び出し元ではこの優先設定が明示的になります。

open System.Runtime.CompilerServices

type Check =
    static member That(condition: bool) =
        if condition then "OK" else "Check failed."

    [<OverloadResolutionPriority(1)>]
    static member That(condition: bool, [<CallerLineNumber>] ?line: int) =
        if condition then "OK" else $"Check failed at line {defaultArg line 0}."

let stock = 0
let diagnostic = Check.That(stock > 0)
// The diagnostic includes the caller's source line.

呼び出しを変更しなくても、行情報対応のオーバーロードが選択されます。 スコープと互換性の規則については、「 オーバーロードの選択」 を参照してください。

.NET の条件付き null 許容性の保証を使用する

ファイル パスが null 以外の場合、Path API の null 許容の結果は、API 自体のコントラクトで起こりえないとされることのチェックを強いるべきではありません。F# 11 は、既存の .NET NotNullIfNotNull promise を使用します。null 以外の名前付き入力は null 以外の結果を意味します。

open System.IO

let fileName: string = Path.GetFileName("reports/annual.csv")
let extension: string = Path.GetExtension(fileName)
printn (extension.ToUpperInvariant())
// .CSV

let fileNameOf (path: string | null) : string | null =
    Path.GetFileName path

<Nullable>enable</Nullable> を有効にします。 Null 許容入力には、引き続き null 許容の処理が必要です。 条件付き戻りコントラクトを参照してください。 制約のない C# T? API の場合は、個別の Unchecked.withNull ヘルパーを使用します。

@kerams の条件付き null 許容のサポートに感謝します。

言語の小規模な改善

レコード型を .NET のコンストラクター構文で呼び出すには、F# 11.2 の レコード コンストラクター を使用します:

type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)

名前付き引数は、フィールドの順序への依存を回避します。 この機能については @charlesroddie に感謝します。

条件付きソースの場合、#elif は入れ子になった #if グループを作らないようにします。

#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif

System.Console.WriteLine(dispatchTimeZone)

最初に一致するブランチが優先されます。 サンプル プロジェクトは EU_WAREHOUSE を定義し、Europe/Paris を出力します。どちらのシンボルも定義されていない場合、出力は UTC になります。

関連するツールと相互運用のケース:

アップグレードする前に

アップグレード時には、次の結果的な変更に注意してください。

影響対象コード アクション
.fsi ファイルを含むライブラリ 適用されたコントラクト属性を署名にコピーする。が FS3888 エラーになりました。
拡張機能ベースのインライン ライブラリ 推論された署名と プロデューサー/コンシューマーの要件を確認します。 F# 11.2 の推論は、より一般的な制約を保持できます。
Instance-method デリゲート null受信側の障害が発生するようになったため、直接構築する前に受信側を検証します。
ローカライズされた補間出力 .NET形式のホールでは、インバリアント カルチャが使用されます。 ローカライズされた出力が意図されている場合は、明示的なカルチャを選択します。
final 付きのカスタム ビルダー do! バインディングおよび単位補完 を追加しました。final do! は、転送先のメンバーを選択しなくなりました。
未完 let! 次の計算本体を追加します。不完全なバインドに、より直接的な診断メッセージが表示されるようになりました。
旧言語設定 サポートされている番号付きモードは F# 8 から始まります。厳密でないインデント モードは削除されます。 言語バージョンとインデントを参照してください。
列挙型のビットごとの演算 整数に基づく列挙型を使用します。無効な基底型は拒否されます。