Elementy członkowskie rozszerzenia dla ograniczeń SRTP

Funkcja śródliniowa ze statycznie rozpoznanymi parametrami typu może wymagać elementu członkowskiego o określonej nazwie i podpisie. Język F# 11.2 umożliwia członkowi rozszerzenia w zakresie spełnianie tego wymagania, nawet jeśli typ pochodzi z innego zestawu.

Ta funkcja umożliwia oddzielenie algorytmu ogólnego od kart dla określonych typów. Nie musisz opakowować tych typów ani dodawać do nich nominalnego interfejsu.

Note

W zestawie SDK .NET 11.0.100 włącz<LangVersion>preview</LangVersion>, gdzie kompilator rozpoznaje rozszerzenia. Zobacz Ustawienia języka F# 11.2 dla planowanego pasma funkcji. Przykłady ścieżki i mapowania używają biblioteki języka F# 11.0 i odbiorcy wersji zapoznawczej; Zobacz Kompilowanie bibliotek i odbiorców.

Dostosowywanie właściwości dla algorytmu współużytkowanego

Poniższy kod aplikacji mierzy ścieżki reprezentowane przez dwa istniejące typy platform. Obie ścieżki mają długość 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

Źródło biblioteki definiuje Structural.Adapters element i Structural.Algorithms. Karty uwidacznia tę samą właściwość współrzędnych dla typów z różnymi natywnymi interfejsami 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

Algorytm pyta o Coordinates właściwość zwracającą parę wartości zmiennoprzecinkowych:

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

W każdym konkretnym wywołaniu kompilator wybiera element Coordinates członkowski, nazywany monitorem ograniczenia. Algorytm wbudowany akceptuje właściwość wewnętrzną lub rozszerzenie; puste i jednopunktowe ścieżki mają długość zero.

Udostępnianie operacji między typami kolekcji

Ograniczenia rozszerzeń obsługują również statyczne elementy członkowskie. Moduł biblioteki Structural.Adapters definiuje karty przy użyciu istniejących List.map funkcji i 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

W module biblioteki Structural.Algorithms zażądaj Map elementu członkowskiego w pełnym typie danych wejściowych:

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

Aplikacja może następnie udostępnić przekształcenie, zmienić typ elementu i utworzyć mapowanie na zagnieżdżoną kolekcję:

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]

Kompilator sprawdza nazwy i typy elementów członkowskich, a nie interfejsy ani prawa mapowania. Nie jest to polimorfizm bardziej uprzejmy: żaden parametr nie reprezentuje konstruktora kontenera, takiego jak F<_>.

Rozszerzenia operatorów

Operatory mogą używać tego samego mechanizmu. Na przykład zdefiniuj operator mapowania dla list i opcji:

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

Użytkownik zostanie otwarty StructuralOperators i użyje operatora :

open StructuralOperators

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

Wbudowane rozwiązania operatorów pierwotnych zachowują priorytet dla pasujących typów. Rozszerzenie nie zapewnia dowolnego rozszerzenia z liczbą mieszaną ani nie zastępuje wszystkich wbudowanych operatorów.

Tryb F# 11.0 może akceptować zwykłą deklarację operatora rozszerzenia z fs1215, ale nie może używać go jako rozwiązania SRTP. Projekt operatora używa wersji zapoznawczej.

Operatory krotki i kontekst

Kolekcja obserwacji i para niezależnie skumulowanych wyników może uczestniczyć w tym samym protokole kombinacji. Następujący użytkownik definiuje swój algorytm ogólny po otwarciu kart biblioteki:

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

Te rozszerzenia bibliotek zapewniają operatorowi znaczenie dla liczb całkowitych, list i par:

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 wymaga listy nonempty, ponieważ używa List.reduce. Operacja zachowuje kolejność listy; kompilator nie wymusza asocjacji ani nie dostarcza tożsamości dla pustych danych wejściowych.

Nagłówki type ('T1 * 'T2) with natywne i type struct ('T1 * 'T2) with rozszerzają odwołania CLR i krotki wartości. Oba są funkcjami języka F# 11.2 i obsługują dwa do siedmiu elementów; większe nagłówki generują fs3915.

Krotki mogą również przenosić kontekst obok wartości. Następujący operator dołącza ślad podczas stosowania funkcji kontekstowej:

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)

Wywołania krotki dot-instance i natywne nagłówki typu funkcji, takie jak type ('a -> 'b) with nie są obsługiwane.

Biblioteka kompozycji i użytkownik używają wersji zapoznawczej. Zachowaj combineAll w konsumentu: eksportowanie tej redukcji może produkować FS0193 dla krotki ++. Oddzielne przykłady ścieżki i mapowania eksportują swoje algorytmy.

Konstrukcja skierowana do powrotu

W przypadku operacji, która wprowadza zwykłą wartość do kontenera, typ danych wyjściowych może określić, który element członkowski powinien go skonstruować:

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"

Adnotacja wyniku odbiorcy wybiera rozszerzenie typu Return danych wyjściowych. Dane wejściowe nie mogą wybrać kontenera.

Każdy kontener dostarcza własny element członkowski. W przypadku przeciążeń tylko zwracanych dla pojedynczego typu należy użyć oddzielnego AllowOverloadOnReturnType atrybutu.

Kompilowanie bibliotek i użytkowników

Ustawienia języka są stosowane dla każdego projektu, a nie przechodnio za pomocą odwołań. Poniższa macierz używa ustawień zestawu SDK .NET 11.0.100 dla funkcji języka F# 11.2:

Implementacja biblioteki Użycie konsumenta Wymaganie dotyczące wersji zapoznawczej
Deklaruje natywne nagłówki rozszerzeń krotki. Rozwiązuje swoje operatory rozszerzeń. Obie wymagają wersji zapoznawczej; nowa składnia nagłówka jest dostępna w wersji zapoznawczej.
Definiuje zwykłe rozszerzenia i eksportuje nierozwiązane ograniczenie wbudowanego elementu członkowskiego. Dostarcza konkretne typy, których rozszerzenia rozwiązuje to ograniczenie. Użytkownik potrzebuje wersji zapoznawczej. Biblioteka może używać języka F# 11.0.
Rozwiązuje ograniczenie rozszerzenia wewnętrznie i uwidacznia zwykły lub już rozwiązany wbudowany interfejs API. Wywołuje ten interfejs API bez dalszego rozwiązywania problemów z rozszerzeniem. Biblioteka wymaga wersji zapoznawczej; użytkownik może używać języka F# 11.0.
Rozwiązuje ograniczenia rozszerzeń i eksportów, które użytkownik rozwiązuje również za pomocą rozszerzeń. Rozwiązuje pozostałe ograniczenia. Obie muszą mieć wersję zapoznawcza.

Biblioteka ścieżek i mapowania używa 11.0elementu ; jego odbiorca używa elementu preview. W katalogu odbiorców uruchom dotnet run polecenie z pomocniczym zestawem SDK języka F# 11.

Zakres rozszerzenia między zestawami

Użyj rozszerzeń publicznych i otwórz swój moduł przed zdefiniowaniem zależnych funkcji wbudowanych. W przypadku zaimportowanego ograniczenia nierozwiązanego otwórz również karty u konsumenta; deklaracje producenta open nie są przenoszone. Interfejs API, który został już rozwiązany, nie wymaga ponownego otwarcia kart przez użytkownika.

Wnioskowanie i podpisy

Rozdzielczość języka F# 11.2 odchyli niektóre opcje, które tryb F# 11.0 wykonuje wcześniej. Rozważ użycie wbudowanego pomocnika zdefiniowanego za pomocą odbiornika DateTime :

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

W trybie F# 11.0 ten formularz może wywnioskować DateTime -> TimeSpan -> DateTime. Po włączeniu rozpoznawania rozszerzeń języka F# 11.2 można zachować ograniczenie dla programu (DateTime or ^Offset) : (static member (+) : DateTime * ^Offset -> ^Result). Użytkownik może następnie wybrać publiczne rozszerzenie zakresu dla innego typu przesunięcia.

Użytkownik nie może odzyskać uogólnienia utraconego w bibliotece języka F# 11.0. Sprawdzanie wyeksportowanych podpisów; Producent może również wymagać włączenia wnioskowania F# 11.2.

Ograniczenia wersji zapoznawczej i rozwiązywanie problemów

  • W przypadku fs0001 lub FS0193 sprawdź ustawienia języka, widoczność publiczną i zakres adaptera.
  • Jeśli późniejszy plik producenta rozpozna przykład ścieżki dla Complex, generowanie kodu może zakończyć się niepowodzeniem z FS0971, Undefined value 'get_Coordinates'. Zachowaj to rozwiązanie przy użyciu jego definicji lub w osobnym odbiorcy.
  • Konkurencyjne karty mogą sprawić, że wybór elementu członkowskiego będzie niejednoznaczny.

Informacje dodatkowe