SRTP 制約の拡張メンバー

静的に解決される型パラメーターを持つインライン関数には、特定の名前とシグネチャを持つメンバーが必要な場合があります。 F# 11.2 では、型が別のアセンブリから取得された場合でも、スコープ内の拡張メンバーがその要件を満たすことができます。

この機能を使用して、汎用アルゴリズムを特定の型のアダプターから分離します。 これらの型をラップしたり、標準インターフェイスを追加したりする必要はありません。

Note

.NET 11.0.100 SDK で、コンパイラが拡張機能を解決する<LangVersion>preview</LangVersion>を有効にします。 計画された機能バンドについては、 F# 11.2 言語設定 を参照してください。 パスとマッピングの例では、F# 11.0 ライブラリとプレビュー コンシューマーを使用します。「 ライブラリとコンシューマーのコンパイル」を参照してください。

共有アルゴリズムのプロパティを調整する

次のアプリケーション コードは、2 つの既存のフレームワークの種類で表されるパスを測定します。 両方のパスの長さは 8です。

open System.Numerics
open Structural.Adapters
open Structural.Algorithms

let complexRoute =
    [ Complex(0.0, 0.0); Complex(3.0, 4.0); Complex(6.0, 4.0) ]
let vectorRoute =
    [ Vector2(0.0f, 0.0f); Vector2(3.0f, 4.0f); Vector2(6.0f, 4.0f) ]

printn (pathLength complexRoute) // 8
printn (pathLength vectorRoute)  // 8

ライブラリ ソースは、Structural.AdaptersとStructural.Algorithmsを定義します。 アダプターは、異なるネイティブ API を持つ型に対して同じ座標プロパティを公開します。

type System.Numerics.Complex with
    member point.Coordinates = point.Real, point.Imaginary

type System.Numerics.Vector2 with
    member point.Coordinates = float point.X, float point.Y

アルゴリズムは、浮動小数点値のペアを返す Coordinates プロパティを要求します。

let inline coordinates (point: ^Point) : float * float =
    (^Point : (member Coordinates: float * float) point)

let inline pathLength (points: ^Point list) =
    points
    |> List.pairwise
    |> List.sumBy (fun (a, b) ->
        let ax, ay = coordinates a
        let bx, by = coordinates b
        let dx, dy = bx - ax, by - ay
        sqrt (dx * dx + dy * dy))

コンパイラは、具体的な呼び出しのたびに、制約のミラーリング監視と呼ばれるCoordinatesメンバーを選択します。 インライン アルゴリズムは、組み込みプロパティまたは拡張プロパティのいずれかを受け入れます。空のパスと単一ポイントパスの長さは 0 です。

コレクション型間で操作を共有する

拡張制約では、静的メンバーもサポートされます。 ライブラリの Structural.Adapters モジュールは、既存の List.map 関数と Option.map 関数を使用してアダプターを定義します。

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))

その後、アプリケーションは、変換を共有し、要素の種類を変更し、入れ子になったコレクションに対するマッピングを作成できます。

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]

コンパイラは、インターフェイスやマッピングの法律ではなく、メンバーの名前と型をチェックします。 これは、より高い種類のポリモーフィズムではありません。 F<_>などのコンテナー コンストラクターを表すパラメーターはありません。

演算子の拡張機能

演算子は同じメカニズムを使用できます。 たとえば、リストとオプションのマッピング演算子を定義します。

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

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

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

コンシューマーが StructuralOperators を開き、演算子を使用します。

open StructuralOperators

let incremented: int list = [1; 2; 3] |>> ((+) 10)
let length: int option = Some "hello" |>> String.length
// [11; 12; 13] and Some 5

組み込みのプリミティブ演算子ソリューションでは、一致する型の優先順位が保持されます。 拡張機能では、任意の数値混合拡大またはすべての組み込み演算子の置換は提供されません。

F# 11.0 モードでは、FS1215 で通常の拡張演算子宣言を受け入れることはできますが、SRTP ソリューションとして使用することはできません。 オペレーター プロジェクトはプレビューを使用します。

タプル演算子とコンテキスト

観測値のコレクションと、独立して蓄積された結果のペアは、同じ組み合わせプロトコルに参加できます。 次のコンシューマーは、ライブラリのアダプターを開いた後、その汎用アルゴリズムを定義します。

open Composition.Adapters
open Composition.Algorithms

let inline combineAll (batches: ^T list) : ^T =
    List.reduce (++) batches

let observations = combineAll [["temperature"]; ["pressure"; "humidity"]]
let summary = combineAll [(2, ["temperature"]); (3, ["pressure"; "humidity"])]

printfn "%A" observations
printfn "%A" summary
// ["temperature"; "pressure"; "humidity"]
// (5, ["temperature"; "pressure"; "humidity"])

これらのライブラリ拡張は、演算子に整数、リスト、およびペアの意味を与えます。

type System.Int32 with
    static member (++) (left: int, right: int) = left + right

type List<'T> with
    static member (++) (left: 'T list, right: 'T list) = left @ right

type ('T1 * 'T2) with
    static member inline (++) ((a, b), (c, d)) = (a ++ c, b ++ d)

combineAll は、 List.reduceを使用するため、空でないリストが必要です。 この操作では、リストの順序が保持されます。コンパイラは、結合規則を適用したり、空の入力の ID を指定したりしません。

ネイティブ ヘッダー type ('T1 * 'T2) with し、CLR の参照タプルと値タプルを拡張 type struct ('T1 * 'T2) with 。 どちらも F# 11.2 の機能であり、2 から 7 つの要素をサポートしています。大きいヘッダーでは FS3915 が生成されます。

タプルは、コンテキストを値と共に運ぶこともできます。 次の演算子は、コンテキスト関数の適用中に証跡を追加します。

let priced =
    (["catalogue price"], fun units -> decimal units * 12m)
        <*> (["validated quantity"], 3)

printfn "%A" priced
// (["catalogue price"; "validated quantity"], 36M)
type ('Context * 'Value) with
    static member inline (<*>) ((before, mapping), (after, value)) =
        (before ++ after, mapping value)

type struct ('Context * 'Value) with
    static member inline (<*>) (struct (before, mapping), struct (after, value)) =
        struct (before ++ after, mapping value)

タプルのドット インスタンス呼び出しとネイティブ関数型ヘッダー ( type ('a -> 'b) with など) はサポートされていません。

コンポジション ライブラリとコンシューマーはどちらもプレビューを使用します。 コンシューマーに combineAll を保持します。その削減をエクスポートすると、タプル ++の FS0193 が生成される可能性があります。 個別のパスとマッピングの例では、アルゴリズムをエクスポートします。

戻り値指示型の構築

コンテナーにプレーンな値を導入する操作の場合、出力の種類は、作成するメンバーを識別できます。

type List<'T> with
    static member Return(value: 'T) : 'T list = [value]

type Option<'T> with
    static member Return(value: 'T) : 'T option = Some value
let inline singleton (value: 'T) : ^Output =
    (^Output : (static member Return: 'T -> ^Output) value)
let oneBatch: string list = singleton "ready"
let oneOptional: string option = singleton "ready"
// ["ready"] and Some "ready"

コンシューマーの結果注釈は、出力の種類の Return 拡張機能を選択します。 入力だけではコンテナーを選択できません。

各コンテナーは、独自のメンバーを提供します。 1 つの型に対する戻り専用オーバーロードの場合は、個別の AllowOverloadOnReturnType 属性を使用します。

ライブラリとコンシューマーをコンパイルする

言語設定は、参照を通じて推移的ではなく、プロジェクトごとに適用されます。 次の表では、F# 11.2 機能に対する .NET 11.0.100 SDK の設定を使用します。

ライブラリの実装 コンシューマーの使用 プレビュー要件
ネイティブタプル拡張ヘッダーを宣言します。 拡張演算子を解決します。 どちらもプレビューが必要です。新しいヘッダー構文自体はプレビューです。
通常の拡張を定義し、未解決のインライン メンバー制約をエクスポートします。 拡張機能がその制約を解決する具象型を提供します。 コンシューマーにはプレビューが必要です。 ライブラリでは F# 11.0 を使用できます。
内部的に拡張制約を解決し、通常または既に解決済みのインライン API を公開します。 拡張機能の解決を行わずに、その API を呼び出します。 ライブラリにはプレビューが必要です。コンシューマーは F# 11.0 を使用できます。
コンシューマーが拡張機能を通じて解決する拡張機能とエクスポートの制約を解決します。 残りの制約を解決します。 どちらもプレビューが必要です。

パスとマッピング ライブラリ は 11.0を使用し、 コンシューマー は previewを使用します。 コンシューマー ディレクトリから、サポートする F# 11 SDK を使用して dotnet run を実行します。

アセンブリ間の拡張スコープ

依存するインライン関数を定義する前に、パブリック拡張機能を使用してモジュールを開きます。 インポートされた未解決の制約の場合は、コンシューマーでアダプターも開きます。プロデューサーの open 宣言は転送されません。 既に解決済みの API では、コンシューマーがアダプターを再度開く必要はありません。

推論と署名

F# 11.2 の解像度では、F# 11.0 モードが以前に行ったいくつかの選択肢が延期されます。 DateTimeレシーバーで定義されたインライン ヘルパーを考えてみましょう。

let inline shift (date: System.DateTime) offset = date + offset

F# 11.0 モードでは、このフォームは DateTime -> TimeSpan -> DateTime推論できます。 F# 11.2 拡張機能の解決を有効にすると、 (DateTime or ^Offset) : (static member (+) : DateTime * ^Offset -> ^Result)の制約を保持できます。 コンシューマーは、別のオフセット型のスコープ内のパブリック拡張機能を選択できます。

コンシューマーは、F# 11.0 ライブラリで失われた一般性を回復できません。 エクスポートされた署名を確認します。プロデューサーでは、F# 11.2 推論を有効にする必要がある場合もあります。

プレビューの制限事項とトラブルシューティング

  • FS0001 または FS0193 の場合は、言語設定、パブリック可視性、アダプター スコープを確認します。
  • 後のプロデューサー ファイルが Complexのパスの例を解決した場合、FS0971、 Undefined value 'get_Coordinates'でコードの生成が失敗する可能性があります。 その定義を使用して、または別のコンシューマーでその解決を維持します。
  • 競合するアダプターでは、メンバーの選択があいまいになる可能性があります。

参照