Tilläggsmedlemmar för SRTP-begränsningar

En infogad funktion med statiskt lösta typparametrar kan kräva en medlem med ett visst namn och en signatur. Med F# 11.2 kan en medlem i omfångstillägget uppfylla det kravet, även när typen kommer från en annan sammansättning.

Använd den här funktionen för att separera en generisk algoritm från kort för vissa typer. Du behöver inte omsluta dessa typer eller lägga till ett nominellt gränssnitt till dem.

Note

I .NET 11.0.100 SDK aktiverar du <LangVersion>preview</LangVersion> var kompilatorn löser tillägg. Se Språkinställningar för F# 11.2 för det planerade funktionsbandet. Sökvägen och mappningsexemplen använder ett F# 11.0-bibliotek och en förhandsgranskningskonsument. se Kompilera bibliotek och konsumenter.

Anpassa en egenskap för en delad algoritm

Följande programkod mäter sökvägar som representeras av två befintliga ramverkstyper. Båda sökvägarna har längden 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

Bibliotekskällan definierar Structural.Adapters och Structural.Algorithms. Korten exponerar samma koordinategenskap för typer med olika interna API:er:

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

Algoritmen ber om en Coordinates egenskap som returnerar ett par flyttalsvärden:

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

Vid varje konkret anrop väljer kompilatorn en Coordinates medlem som kallas begränsningens vittne. Den infogade algoritmen accepterar antingen en inbyggd egenskap eller en tilläggsegenskap. tomma sökvägar och enkelpunktssökvägar har längd noll.

Dela en åtgärd mellan samlingstyper

Tilläggsbegränsningar stöder även statiska medlemmar. Bibliotekets Structural.Adapters modul definierar kort med hjälp av befintliga List.map och Option.map funktioner:

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

I bibliotekets Structural.Algorithms modul begär du en Map medlem av den fullständiga indatatypen:

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

Programmet kan sedan dela en transformering, ändra elementtypen och skapa mappning över en kapslad samling:

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]

Kompilatorn kontrollerar medlemsnamn och typer, inte gränssnitt eller mappningslagar. Det här är inte polymorfism med högre typ: ingen parameter representerar en containerkonstruktor, till exempel F<_>.

Operatortillägg

Operatorer kan använda samma mekanism. Definiera till exempel en mappningsoperator för listor och alternativ:

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

En konsument öppnar StructuralOperators och använder operatorn:

open StructuralOperators

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

Inbyggda primitiva operatorlösningar behåller prioriteten för matchande typer. Ett tillägg ger inte godtycklig mixad numerisk breddning eller ersätter alla inbyggda operatorer.

F# 11.0-läget kan acceptera en vanlig tilläggsoperatordeklaration med FS1215, men kan inte använda den som en SRTP-lösning. Operatorprojektet använder förhandsversionen.

Tuppelns operatorer och kontext

En samling observationer och ett par oberoende ackumulerade resultat kan delta i samma kombinationsprotokoll. Följande konsument definierar sin allmänna algoritm när bibliotekets kort har öppnats:

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

Dessa bibliotekstillägg ger operatorn en betydelse för heltal, listor och 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 kräver en icke-lista eftersom den använder List.reduce. Åtgärden bevarar listordningen. kompilatorn framtvingar inte associativitet eller anger en identitet för tomma indata.

Interna rubriker type ('T1 * 'T2) with och type struct ('T1 * 'T2) with utöka CLR-referens och värdetupplar. Båda är F# 11.2-funktioner och har stöd för två till sju element. större rubriker producerar FS3915.

Tupplar kan också ha kontext tillsammans med ett värde. Följande operator lägger till ett spår när en kontextbaserad funktion används:

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)

Tuppelns punktinstansanrop och interna sidhuvuden av funktionstyp, till exempel type ('a -> 'b) with stöds inte.

Både sammansättningsbiblioteket och konsumenten använder förhandsversion. Behåll combineAll i konsumenten: exportera den minskningen kan producera FS0193 för tuppeln ++. De separata sökvägarna och mappningsexemplen exporterar sina algoritmer.

Returriktad konstruktion

För en åtgärd som introducerar ett vanligt värde i en container kan utdatatypen identifiera vilken medlem som ska konstruera det:

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"

Konsumentens resultatanteckning väljer utdatatypens Return tillägg. Enbart indata kan inte välja en container.

Varje container levererar sin egen medlem. Använd det separata AllowOverloadOnReturnType attributet för att endast returnera överlagringar av en enskild typ.

Kompilera bibliotek och konsumenter

Språkinställningar gäller per projekt, inte transitivt via referenser. I följande matris används inställningarna för SDK .NET 11.0.100 för F# 11.2-funktioner:

Biblioteksimplementering Konsumentanvändning Förhandsversionskrav
Deklarerar inbyggda sidhuvuden för tuppelns tillägg. Löser deras tilläggsoperatorer. Båda behöver förhandsversion; den nya huvudsyntaxen i sig är förhandsversion.
Definierar vanliga tillägg och exporterar en ouppklarad infogad medlemsbegränsning. Tillhandahåller konkreta typer vars tillägg löser den begränsningen. Konsumenten behöver förhandsversion. Biblioteket kan använda F# 11.0.
Löser en tilläggsbegränsning internt och exponerar ett vanligt eller redan löst infogat API. Anropar api:et utan ytterligare tilläggsmatchning. Biblioteket behöver förhandsversion; konsumenten kan använda F# 11.0.
Löser tillägg och exportbegränsningar som konsumenten också löser genom tillägg. Löser återstående begränsningar. Båda behöver förhandsversion.

Sökvägen och mappningsbiblioteket använder 11.0; dess konsument använder preview. Från konsumentkatalogen kör du dotnet run med ett stöd för F# 11 SDK.

Tilläggsomfång för sammansättningar

Använd offentliga tillägg och öppna modulen innan du definierar beroende infogade funktioner. För en importerad ouppklarad begränsning öppnar du även korten i konsumenten. producentens open deklarationer inte överförs. Ett redan löst API kräver inte att konsumenten öppnar sina kort igen.

Slutsatsdragning och signaturer

F# 11.2-upplösning defererar vissa alternativ som F# 11.0-läget gör tidigare. Överväg att använda en infogad hjälp som definierats med en DateTime mottagare:

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

I F# 11.0-läge kan formuläret härleda DateTime -> TimeSpan -> DateTime. Med F# 11.2-tilläggsmatchning aktiverat kan det behålla en begränsning för (DateTime or ^Offset) : (static member (+) : DateTime * ^Offset -> ^Result). En konsument kan sedan välja ett offentligt tillägg inom omfånget för en annan förskjutningstyp.

En konsument kan inte återställa generalitet som förlorats i ett F# 11.0-bibliotek. Kontrollera exporterade signaturer. producenten kan också behöva F# 11.2-inferens aktiverad.

Förhandsversionsbegränsningar och felsökning

  • För FS0001 eller FS0193 kontrollerar du språkinställningar, offentlig synlighet och adapteromfång.
  • Om en senare producentfil löser sökvägsexemplet för Complexkan kodgenereringen misslyckas med FS0971, Undefined value 'get_Coordinates'. Behåll den lösningen med dess definitioner eller i en separat konsument.
  • Konkurrerande kort kan göra medlemsvalet tvetydigt.

Se även