Generika
F#-funktionsvärden, metoder, egenskaper och aggregeringstyper som klasser, poster och diskriminerade fackföreningar kan vara generiska. Generiska konstruktioner innehåller minst en typparameter, som vanligtvis tillhandahålls av användaren av den generiska konstruktionen. Med allmänna funktioner och typer kan du skriva kod som fungerar med en mängd olika typer utan att upprepa koden för varje typ. Det kan vara enkelt att göra koden generisk i F#, eftersom koden ofta implicit härleds till att vara generisk av kompilatorns typinferens och automatiska generaliseringsmekanismer.
Syntax
// Explicitly generic function.
let function-name<type-parameters> parameter-list =
function-body
// Explicitly generic method.
[ static ] member object-identifier.method-name<type-parameters> parameter-list [ return-type ] =
method-body
// Explicitly generic class, record, interface, structure,
// or discriminated union.
type type-name<type-parameters> type-definition
Kommentarer
Deklarationen av en explicit allmän funktion eller typ liknar den för en icke-generisk funktion eller typ, med undantag för specifikationen (och användningen) av typparametrarna, inom vinkelparenteser efter funktionen eller typnamnet.
Deklarationer är ofta implicit allmänna. Om du inte helt anger vilken typ av parameter som används för att skapa en funktion eller typ försöker kompilatorn härleda typen av varje parameter, värde och variabel från koden som du skriver. Mer information finns i Typ slutsatsdragning. Om koden för din typ eller funktion inte på annat sätt begränsar typerna av parametrar är funktionen eller typen implicit allmän. Den här processen kallas automatisk generalisering. Det finns vissa begränsningar för automatisk generalisering. Om F#-kompilatorn till exempel inte kan härleda typerna för en generisk konstruktion rapporterar kompilatorn ett fel som refererar till en begränsning som kallas värdebegränsningen. I så fall kan du behöva lägga till några typanteckningar. Mer information om automatisk generalisering och värdebegränsning och hur du ändrar koden för att åtgärda problemet finns i Automatisk generalisering.
I den tidigare syntaxen är type-parameters en kommaavgränsad lista med parametrar som representerar okända typer, som var och en börjar med ett enkelt citattecken, om du vill med en begränsningssats som ytterligare begränsar vilka typer som kan användas för den typparametern. Syntaxen för villkorssatser av olika slag och annan information om begränsningar finns i Begränsningar.
Typdefinitionen i syntaxen är samma som typdefinitionen för en icke-generisk typ. Den innehåller konstruktorparametrarna för en klasstyp, en valfri as
sats, likhetssymbolen, postfälten inherit
, -satsen, alternativen för en diskriminerad union let
och do
bindningar, medlemsdefinitioner och allt annat som tillåts i en icke-generisk typdefinition.
De andra syntaxelementen är samma som för icke-generiska funktioner och typer. Objektidentifierare är till exempel en identifierare som representerar själva det innehållande objektet.
Egenskaper, fält och konstruktorer kan inte vara mer generiska än omslutningstypen. Dessutom kan värden i en modul inte vara generiska.
Implicit generiska konstruktioner
När F#-kompilatorn härleder typerna i koden behandlar den automatiskt alla funktioner som kan vara generiska som generiska. Om du uttryckligen anger en typ, till exempel en parametertyp, förhindrar du automatisk generalisering.
I följande kodexempel är generiskt makeList
, även om varken det eller dess parametrar uttryckligen deklareras som generiska.
let makeList a b =
[a; b]
Signaturen för funktionen härleds till 'a -> 'a -> 'a list
. Observera att a
och b
i det här exemplet härleds att ha samma typ. Detta beror på att de ingår i en lista tillsammans, och alla element i en lista måste vara av samma typ.
Du kan också göra en funktion allmän genom att använda syntaxen för enkla citattecken i en typanteckning för att ange att en parametertyp är en generisk typparameter. I följande kod function1
är allmän eftersom dess parametrar deklareras på det här sättet, som typparametrar.
let function1 (x: 'a) (y: 'a) =
printfn "%A %A" x y
Explicit generiska konstruktioner
Du kan också göra en funktion generisk genom att uttryckligen deklarera dess typparametrar inom vinkelparenteser (<type-parameter>
). Följande kod illustrerar detta.
let function2<'T> (x: 'T) (y: 'T) =
printfn "%A, %A" x y
Använda allmänna konstruktioner
När du använder allmänna funktioner eller metoder kanske du inte behöver ange typargumenten. Kompilatorn använder typinferens för att härleda lämpliga typargument. Om det fortfarande finns en tvetydighet kan du ange typargument inom vinkelparenteser och separera flera typargument med kommatecken.
Följande kod visar användningen av de funktioner som definieras i föregående avsnitt.
// In this case, the type argument is inferred to be int.
function1 10 20
// In this case, the type argument is float.
function1 10.0 20.0
// Type arguments can be specified, but should only be specified
// if the type parameters are declared explicitly. If specified,
// they have an effect on type inference, so in this example,
// a and b are inferred to have type int.
let function3 a b =
// The compiler reports a warning:
function1<int> a b
// No warning.
function2<int> a b
Anteckning
Det finns två sätt att referera till en allmän typ efter namn. Och är till exempel int list
list<int>
två sätt att referera till en allmän typ list
som har ett argument int
av en enda typ . Det senare formuläret används endast konventionellt med inbyggda F#-typer som list
och option
. Om det finns flera typargument använder du normalt syntaxen Dictionary<int, string>
men du kan också använda syntaxen (int, string) Dictionary
.
Jokertecken som typargument
Om du vill ange att ett typargument ska härledas av kompilatorn kan du använda understrecket eller jokertecknet (_
) i stället för ett namngivet typargument. Detta visas i följande kod.
let printSequence (sequence1: Collections.seq<_>) =
Seq.iter (fun elem -> printf "%s " (elem.ToString())) sequence1
Begränsningar i allmänna typer och funktioner
I en allmän typ eller funktionsdefinition kan du bara använda de konstruktioner som är kända för att vara tillgängliga för den generiska typparametern. Detta krävs för att aktivera verifiering av funktions- och metodanrop vid kompileringstillfället. Om du deklarerar dina typparametrar explicit kan du tillämpa en uttrycklig begränsning på en allmän typparameter för att meddela kompilatorn att vissa metoder och funktioner är tillgängliga. Men om du tillåter att F#-kompilatorn härleder dina allmänna parametertyper avgör den lämpliga begränsningar för dig. Mer information finns i Begränsningar.
Statiskt lösta typparametrar
Det finns två typer av typparametrar som kan användas i F#-program. Den första är generiska typparametrar av den typ som beskrivs i föregående avsnitt. Den här första typen av typparameter motsvarar de allmänna typparametrar som används på språk som Visual Basic och C#. En annan typparameter är specifik för F# och kallas för en statiskt matchad typparameter. Information om dessa konstruktioner finns i Statiskt lösta typparametrar.
Exempel
// A generic function.
// In this example, the generic type parameter 'a makes function3 generic.
let function3 (x : 'a) (y : 'a) =
printf "%A %A" x y
// A generic record, with the type parameter in angle brackets.
type GR<'a> =
{
Field1: 'a;
Field2: 'a;
}
// A generic class.
type C<'a>(a : 'a, b : 'a) =
let z = a
let y = b
member this.GenericMethod(x : 'a) =
printfn "%A %A %A" x y z
// A generic discriminated union.
type U<'a> =
| Choice1 of 'a
| Choice2 of 'a * 'a
type Test() =
// A generic member
member this.Function1<'a>(x, y) =
printfn "%A, %A" x y
// A generic abstract method.
abstract abstractMethod<'a, 'b> : 'a * 'b -> unit
override this.abstractMethod<'a, 'b>(x:'a, y:'b) =
printfn "%A, %A" x y