F# 6 では、F# 言語と F# Interactive にいくつかの機能強化が追加されています。 .NET 6 でリリースされます。
最新の .NET SDK は 、.NET ダウンロード ページからダウンロードできます。
概要
F# 6 は、すべての .NET Core ディストリビューションと Visual Studio ツールで使用できます。 詳細については、「 F# の概要」を参照してください。
タスク {...}
F# 6 には、 タスク式を使用して F# コードで .NET タスクを作成するためのネイティブ サポートが含まれています。 タスク式は非同期式に似ていますが、.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
}
優れた TaskBuilder.fs ライブラリと Ply ライブラリを通じて、F# 5 でタスクのサポートを利用できます。 組み込みのサポートにコードを移行するのは簡単です。 ただし、いくつかの違いがあります。名前空間と型推論は、組み込みのサポートとこれらのライブラリの間で若干異なり、いくつかの追加の型注釈が必要になる場合があります。 必要に応じて、これらのコミュニティ ライブラリを F# 6 で明示的に参照し、各ファイルで正しい名前空間を開く場合は、引き続き F# 6 で使用できます。
task {…}の使用は、async {…}の使用とよく似ています。
task {…}の使用には、async {…}よりもいくつかの利点があります。
-
task {...}のオーバーヘッドが低くなり、非同期処理が迅速に実行されるホット コード パスのパフォーマンスが向上する可能性があります。 -
task {…}用のステップ実行とスタックトレースのデバッグがより良好です。 - タスクを想定または生成する .NET パッケージとの相互運用が容易になります。
async {…}に慣れている場合は、次の点に注意する必要があります。
-
task {…}は、タスクを最初の待機ポイントまで直ちに実行します。 -
task {…}はキャンセル トークンを暗黙的に伝達しません。 -
task {…}では、暗黙的な取り消しチェックは実行されません。 -
task {…}は非同期のテールコールをサポートしていません。 つまり、return! ..を再帰的に使用すると、中間の非同期待機がない場合にスタック オーバーフローが発生する可能性があります。
一般に、タスクを使用する .NET ライブラリと相互運用する場合や、非同期コード のテールコールや暗黙的なキャンセル トークンの伝達に依存しない場合は、新しいコードでtask {…}よりもasync {…}を使用することを検討する必要があります。 既存のコードでは、前に説明したtask {…}の特性に依存しないようにコードを確認した後にのみ、async {…}に切り替える必要があります。
この機能は 、F# RFC FS-1097 を実装します。
を使用した簡単なインデックス作成構文 expr[idx]
F# 6 では、コレクションのインデックス作成とスライスの構文 expr[idx] を使用できます。
F# 5 まで、F# ではインデックス作成構文として expr.[idx] が使用されています。
expr[idx]の使用を許可することは、F# を学習する人や初めて F# に触れる人からのフィードバックに基づいています。彼らの多くは、ドット表記のインデックス付けの使用が業界標準からの不要な逸脱と感じています。
既定では、 expr.[idx]の使用に関する警告は生成されないため、これは破壊的変更ではありません。 ただし、コードの明確化を示唆する一部の情報メッセージが出力されます。 必要に応じて、さらに情報メッセージをアクティブ化することもできます。 たとえば、オプションの情報警告 (/warnon:3366) をアクティブにして、 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 つの利点があります。
- 必要な明示的なアップキャストの数を減らします
- 必要な明示的な整数変換の数を減らします
- .NETスタイルの暗黙的な型変換に対するファーストクラスのサポートが追加される
この機能は 、F# RFC FS-1093 を実装します。
追加の暗黙的なアップキャスト変換
F# 6 では、追加の暗黙的なアップキャスト変換が実装されています。 たとえば、F# 5 以前のバージョンでは、型注釈が存在する場合でも、式が異なる分岐で異なるサブタイプを持つ関数を実装するときに、戻り値の式にアップキャストが必要でした。 次の 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
ここでは、条件付き文の分岐がそれぞれ TextReader と StreamReader を計算し、両方の分岐に 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 では、int32からint64への拡大、int32へのnativeint、int32へのdoubleが自動的に行われます。これは、型の推論中にソースと宛先の両方の型がわかっている場合です。 そのため、前の例のような場合は、 int32 リテラルを使用できます。
Tensor.Create([100; 10; 10])
この変更にもかかわらず、ほとんどの場合、F# は数値型の明示的な拡大を引き続き使用します。 たとえば、暗黙的な拡大は、 int8 や int16などの他の数値型、 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を有効にすることができます。
注
F# 6 の最初のリリースでは、この警告番号は /warnon:3390 でした。 競合のため、警告番号は後で /warnon:3395に更新されました。
暗黙的な変換に関するオプションの警告
型主導型変換と暗黙的な変換は、型推論と十分にやり取りできない可能性があり、理解が難しいコードにつながる可能性があります。 このため、F# コードでこの機能が悪用されないようにするために、いくつかの軽減策が存在します。 最初に、ソースと宛先の両方の型を厳密に認識する必要があります。あいまいさや追加の型推論は発生しません。 第 2 に、オプトイン警告をアクティブにして、暗黙的な変換の使用を報告できます。既定では 1 つの警告がオンになっています。
-
/warnon:3388(追加の暗黙的なアップキャスト) -
/warnon:3389(暗黙的な数値拡大) -
/warnon:3391(既定では、メソッド以外の引数でop_Implicitが有効) -
/warnon:3395(メソッドの引数内でop_Implicitが適用される)
暗黙的な変換のすべての使用を禁止する場合は、 /warnaserror:3388、 /warnaserror:3389、 /warnaserror:3391、 /warnaserror:3395を指定することもできます。
2 進数の書式設定
F# 6 では、 %B パターンが、2 進数形式で使用可能な書式指定子に追加されます。 次の F# コードについて考えてみましょう。
printf "%o" 123
printf "%B" 123
このコードでは、次の出力が出力されます。
173
1111011
この機能は 、F# RFC FS-1100 を実装します。
使用時のバインド削除
F# 6 では、_ バインドでuseを使用できます。次に例を示します。
let doSomething () =
use _ = System.IO.File.OpenText("input.txt")
printfn "reading the file"
この機能は 、F# RFC FS-1102 を実装します。
インラインイフラムダ
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
- リスト/配列/シーケンス.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 を実装します。
マップにキーと値がある
FSharp.Core 6.0.0 では、 Map 型で Keys プロパティと Values プロパティがサポートされるようになりました。 これらのプロパティは、基になるコレクションをコピーしません。
この機能は 、F# RFC FS-1113 に記載されています。
NativePtr に対する追加の組み込み関数
FSharp.Core 6.0.0 では、 NativePtr モジュールに新しい組み込みが追加されます。
NativePtr.nullPtrNativePtr.isNullPtrNativePtr.initBlockNativePtr.clearNativePtr.copyNativePtr.copyBlockNativePtr.ofILSigPtrNativePtr.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 つの演算子と、参照呼び出しに特に関連 incr 2 つの関数と decr が含まれています。 これらの演算子が存在すると、参照セルは必要以上に 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 を実装します。
Visual Studio でスクリプトでの既定が .NET 6 となる F# ツール
Visual Studio で F# スクリプト (.fsx) を開くか実行した場合、既定では、スクリプトは分析され、64 ビット実行で .NET 6 を使用して実行されます。 この機能は、Visual Studio 2019 の以降のリリースでプレビュー段階にあり、既定で有効になりました。
.NET Framework スクリプトを有効にするには、 ツール>オプション>F# ツール>F# 対話型を選択します。 [.NET Core スクリプトの使用] をfalse に設定し、F# 対話型ウィンドウを再起動します。 この設定は、スクリプトの編集とスクリプトの実行の両方に影響します。 .NET Framework スクリプトの 32 ビット実行を有効にするには、 64 ビット F# Interactive も false に設定します。 .NET Core スクリプトには 32 ビット のオプションはありません。
F# ツール: F# スクリプトの SDK バージョンをピン留めする
.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 システムでは、これを shebang と組み合わせて、スクリプトを直接実行するための言語バージョンも指定できます。
script.fsxにおける簡単なシェバンはこうです。
#!/usr/bin/env -S dotnet fsi
printfn "Hello, world"
script.fsxを使用してスクリプトを直接実行できるようになりました。 これを、次のような特定の既定以外の言語バージョンと組み合わせることができます。
#!/usr/bin/env -S dotnet fsi --langversion:5.0
注
この設定は、最新の言語バージョンを想定してスクリプトを分析する編集ツールでは無視されます。
従来の機能の削除
F# 2.0 以降、非推奨のレガシ機能の中には、長い間警告が表示されているものもあります。 F# 6 でこれらの機能を使用すると、 /langversion:5.0を明示的に使用しない限り、エラーが発生します。 エラーが発生する機能は次のとおりです。
-
(int, int) Dictionaryなど、後置型名を使用する複数のジェネリック パラメーター。 これは F# 6 でエラーになります。 代わりに、標準構文Dictionary<int,int>を使用する必要があります。 -
#indent "off"。 これはエラーになります。 -
x.(expr)。 これはエラーになります。 -
module M = struct … end。 これはエラーになります。 - 入力
*.mlと*.mliの使用。 これはエラーになります。 -
(*IF-CAML*)または(*IF-OCAML*)の使用。 これはエラーになります。 -
land、lor、lxor、lsl、lsr、またはasrをインフィックス演算子として使用します。 これらは F# のインフィックス キーワードです。これは OCaml のインフィックス キーワードであり、FSharp.Core では定義されていないためです。 これらのキーワードを使用すると、警告が出力されるようになりました。
これにより、 F# RFC FS-1114 が実装されます。
.NET