Automatisk generalisering

F# använder typinferens för att utvärdera typerna av funktioner och uttryck. Det här avsnittet beskriver hur F# automatiskt generaliserar argumenten och typerna av funktioner så att de fungerar med flera typer när detta är möjligt.

Automatisk generalisering

F#-kompilatorn avgör om en viss parameter kan vara generisk när den utför typinferens på en funktion. Kompilatorn undersöker varje parameter och avgör om funktionen har ett beroende av den specifika typen av parameter. Om den inte gör det härleds typen till generisk.

Följande kodexempel illustrerar en funktion som kompilatorn härleder till att vara generisk.

let max a b = if a > b then a else b

Typen härleds till 'a -> 'a -> 'a.

Typen anger att det här är en funktion som tar två argument av samma okända typ och returnerar ett värde av samma typ. En av anledningarna till att den tidigare funktionen kan vara generisk är att operatorn större än (>) i sig är generisk. Operatorn större än har signaturen 'a -> 'a -> bool. Alla operatorer är inte generiska, och om koden i en funktion använder en parametertyp tillsammans med en icke-generisk funktion eller operator kan den parametertypen inte generaliseras.

Eftersom max det är allmänt kan det användas med typer som int, floatoch så vidare, som du ser i följande exempel.

let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3

De två argumenten måste dock vara av samma typ. Signaturen är 'a -> 'a -> 'a, inte 'a -> 'b -> 'a. Därför genererar följande kod ett fel eftersom typerna inte matchar.

// Error: type mismatch.
let biggestIntFloat = max 2.0 3

Funktionen max fungerar också med alla typer som stöder operatorn större än. Därför kan du också använda den på en sträng, som du ser i följande kod.

let testString = max "cab" "cat"

Värdebegränsning

Kompilatorn utför endast automatisk generalisering på fullständiga funktionsdefinitioner som har explicita argument och på enkla oföränderliga värden.

Det innebär att kompilatorn utfärdar ett fel om du försöker kompilera kod som inte är tillräckligt begränsad för att vara en viss typ, men som inte heller kan generaliseras. Felmeddelandet för det här problemet refererar till den här begränsningen av automatisk generalisering för värden som värdebegränsning.

Vanligtvis uppstår felet för värdebegränsning antingen när du vill att en konstruktion ska vara generisk men kompilatorn inte har tillräckligt med information för att generalisera den, eller när du oavsiktligt utelämnar tillräcklig typinformation i en icke-generisk konstruktion. Lösningen på värdebegränsningsfelet är att tillhandahålla mer explicit information för att mer fullständigt begränsa typinferensproblemet på något av följande sätt:

  • Begränsa en typ till att vara icke-genererad genom att lägga till en explicit typanteckning i ett värde eller en parameter.

  • Om problemet är att använda en icke-generaliserbar konstruktion för att definiera en generisk funktion, till exempel en funktionssammansättning eller ofullständigt tillämpade curryfunktionsargument, kan du försöka skriva om funktionen som en vanlig funktionsdefinition.

  • Om problemet är ett uttryck som är för komplext för att generaliseras gör du det till en funktion genom att lägga till en extra, oanvänd parameter.

  • Lägg till explicita allmänna typparametrar. Det här alternativet används sällan.

Följande kodexempel illustrerar var och en av dessa scenarier.

Fall 1: För komplext uttryck. I det här exemplet är listan counter avsedd att vara int option ref, men den definieras inte som ett enkelt oföränderligt värde.

let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None

Fall 2: Använda en icke-generaliserbar konstruktion för att definiera en allmän funktion. I det här exemplet är konstruktionen inte allmän eftersom den omfattar delvis tillämpning av funktionsargument.

let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj

Fall 3: Lägga till en extra, oanvänd parameter. Eftersom det här uttrycket inte är tillräckligt enkelt för generalisering utfärdar kompilatorn värdet begränsningsfelet.

let emptyList10 = Array.create 10 []
// Adding an extra (unused) parameter makes it a function, which is generalizable.
let emptyList10 () = Array.create 10 []

Fall 4: Lägga till typparametrar.

let arrayOf10Lists = Array.create 10 []
// Adding a type parameter and type annotation lets you write a generic value.
let arrayOf10Lists<'T> = Array.create 10 ([]:'T list)

I det sista fallet blir värdet en typfunktion, som kan användas för att skapa värden av många olika typer, till exempel på följande sätt:

let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>

Se även