Extensieleden voor SRTP-beperkingen

Een inlinefunctie met statisch opgeloste typeparameters kan een lid met een bepaalde naam en handtekening vereisen. Met F# 11.2 kan een uitbreidingslid binnen het bereik aan die vereiste voldoen, zelfs wanneer het type afkomstig is van een andere assembly.

Gebruik deze mogelijkheid om een algemeen algoritme te scheiden van adapters voor bepaalde typen. U hoeft deze typen niet te verpakken of er een nominale interface aan toe te voegen.

Note

Schakel in de .NET 11.0.100 SDK <LangVersion>preview</LangVersion> in waar de compiler extensies oplost. Zie de taalinstellingen voor F# 11.2 voor de geplande functieband. Het pad en de toewijzingsvoorbeelden maken gebruik van een F# 11.0-bibliotheek en een preview-consument; zie Compileerbibliotheken en consumenten.

Een eigenschap aanpassen voor een gedeeld algoritme

De volgende toepassingscode meet paden die worden vertegenwoordigd door twee bestaande frameworktypen. Beide paden hebben een lengte van 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

De bibliotheekbron definieert Structural.Adapters en Structural.Algorithms. De adapters maken dezelfde coördinateneigenschap beschikbaar voor typen met verschillende systeemeigen API's:

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

Het algoritme vraagt om een Coordinates eigenschap die een paar drijvendekommawaarden retourneert:

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

Bij elke concrete aanroep selecteert de compiler een Coordinates lid, de zogenaamde getuige van de beperking. Het inline-algoritme accepteert een intrinsieke of uitbreidingseigenschap; lege paden en paden met één punt hebben lengte nul.

Een bewerking delen tussen verzamelingstypen

Uitbreidingsbeperkingen ondersteunen ook statische leden. De module van Structural.Adapters de bibliotheek definieert adapters met behulp van de bestaande List.map functies: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

Vraag in de module van Structural.Algorithms de bibliotheek een Map lid aan voor het volledige invoertype:

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

De toepassing kan vervolgens een transformatie delen, het elementtype wijzigen en toewijzing opstellen via een geneste verzameling:

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]

De compiler controleert lidnamen en -typen, niet interfaces of toewijzingswetten. Dit is geen polymorfisme met een hogere soort: geen parameter vertegenwoordigt een containerconstructor zoals F<_>.

Operatorextensies

Operators kunnen hetzelfde mechanisme gebruiken. Definieer bijvoorbeeld een toewijzingsoperator voor lijsten en opties:

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

Een consument wordt geopend StructuralOperators en gebruikt de operator:

open StructuralOperators

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

Ingebouwde primitieve operatoroplossingen behouden prioriteit voor overeenkomende typen. Een extensie biedt geen willekeurige mixed-numerieke widening of vervangt alle ingebouwde operators.

De F# 11.0-modus kan een gewone uitbreidingsoperatordeclaratie met FS1215 accepteren, maar kan deze niet gebruiken als een SRTP-oplossing. Het operatorproject maakt gebruik van preview.

Tuple-operators en -context

Een verzameling waarnemingen en een paar onafhankelijk samengevoegde resultaten kunnen deelnemen aan hetzelfde combinatieprotocol. De volgende consument definieert het algemene algoritme na het openen van de adapters van de bibliotheek:

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

Deze bibliotheekextensies geven de operator een betekenis voor gehele getallen, lijsten en paren:

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 vereist een lege lijst omdat deze wordt gebruikt List.reduce. De bewerking behoudt de volgorde van de lijst; De compiler dwingt geen associativiteit af of levert een identiteit voor een lege invoer.

Systeemeigen headers type ('T1 * 'T2) with en type struct ('T1 * 'T2) with breidt CLR-verwijzing en waarde-tuples uit. Beide zijn F# 11.2-functies en ondersteunen twee tot en met zeven elementen; grotere headers produceren FS3915.

Tuples kunnen ook context naast een waarde dragen. De volgende operator voegt een trail toe tijdens het toepassen van een contextuele functie:

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)

Aanroepen van tuple-dot-instances en systeemeigen functie-type headers, zoals type ('a -> 'b) with worden niet ondersteund.

De samenstellingsbibliotheek en de consument maken beide gebruik van preview. Houd combineAll de consument bij: exporteren die vermindering kan FS0193 voor tuple ++produceren. De afzonderlijke pad- en toewijzingsvoorbeelden exporteren hun algoritmen.

Retour gerichte constructie

Voor een bewerking die een gewone waarde in een container introduceert, kan het uitvoertype bepalen welk lid deze moet samenstellen:

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"

De aantekening van het resultaat van de consument selecteert de extensie van Return het uitvoertype. De invoer alleen kan geen container kiezen.

Elke container levert een eigen lid. Gebruik het afzonderlijke AllowOverloadOnReturnType kenmerk voor overbelastingen met alleen retourneert voor één type.

Bibliotheken en consumenten compileren

Taalinstellingen zijn van toepassing per project, niet transitief via verwijzingen. In de volgende matrix worden de instellingen van de .NET 11.0.100 SDK gebruikt voor F# 11.2-functies:

Bibliotheek-implementatie Consumentengebruik Preview-vereiste
Declareert systeemeigen tuple-extension-headers. Lost de extensieoperators op. Beide hebben preview nodig; de nieuwe headersyntaxis zelf is een voorbeeld.
Hiermee definieert u gewone extensies en exporteert u een onopgeloste inlinelidbeperking. Levert betontypen waarvan de uitbreidingen die beperking oplossen. Voorbeeld van consumentenbehoeften. De bibliotheek kan F# 11.0 gebruiken.
Lost een uitbreidingsbeperking intern op en maakt een gewone of al opgeloste inline-API beschikbaar. Roept die API aan zonder verdere uitbreidingsresolutie. Voorbeeld van bibliotheekbehoeften; de consument kan F# 11.0 gebruiken.
Hiermee lost u uitbreidingen en exportbeperkingen op die de consument ook oplost via extensies. Hiermee worden de resterende beperkingen opgelost. Beide hebben preview nodig.

Het pad en de toewijzingsbibliotheek gebruikt 11.0; de consument gebruikt preview. Voer dotnet run vanuit de consumentendirectory uit met een ondersteunende F# 11 SDK.

Uitbreidingsbereik voor assembly's

Gebruik openbare extensies en open de module voordat u afhankelijke inlinefuncties definieert. Voor een geïmporteerde niet-opgeloste beperking opent u ook de adapters in de consument; de aangiften van open de producent worden niet overgedragen. Voor een al opgeloste API hoeft de consument de adapters niet opnieuw te openen.

Deductie en handtekeningen

Met F# 11.2-resolutie worden sommige keuzes uitgesteld die de F# 11.0-modus eerder maakt. Overweeg een inline helper die is gedefinieerd met een DateTime ontvanger:

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

In de F# 11.0-modus kan dit formulier afleiden DateTime -> TimeSpan -> DateTime. Als F# 11.2-extensieresolutie is ingeschakeld, kan deze een beperking voor (DateTime or ^Offset) : (static member (+) : DateTime * ^Offset -> ^Result). Een consument kan vervolgens een openbare uitbreiding binnen het bereik selecteren voor een ander offsettype.

Een consument kan de algemeenheid die verloren gaat in een F# 11.0-bibliotheek niet herstellen. Geëxporteerde handtekeningen controleren; De producent moet mogelijk ook F# 11.2 deductie inschakelen.

Preview-beperkingen en probleemoplossing

  • Voor FS0001 of FS0193 controleert u de taalinstellingen, de openbare zichtbaarheid en het adapterbereik.
  • Als een later producerbestand het padvoorbeeld oplost Complex, kan het genereren van code mislukken met FS0971, Undefined value 'get_Coordinates'. Bewaar die resolutie met de definities ervan of in een afzonderlijke consument.
  • Concurrerende adapters kunnen lidselectie dubbelzinnig maken.

Zie ook