Sök…
Introduktion till typer
Typer kan representera olika slags saker. Det kan vara en enda data, en uppsättning data eller en funktion.
I F # kan vi gruppera typerna i två kategorier:
F # typer:
// Functions let a = fun c -> c // Tuples let b = (0, "Foo") // Unit type let c = ignore // Records type r = { Name : string; Age : int } let d = { Name = "Foo"; Age = 10 } // Discriminated Unions type du = | Foo | Bar let e = Bar // List and seq let f = [ 0..10 ] let g = seq { 0..10 } // Aliases type MyAlias = string
.NET-typer
- Inbyggd typ (int, bool, sträng, ...)
- Klasser, strukturer och gränssnitt
- delegater
- arrayer
Skriv förkortningar
Typförkortningar låter dig skapa alias på befintliga typer för att ge dem mer meningsfulla sinnen.
// Name is an alias for a string
type Name = string
// PhoneNumber is an alias for a string
type PhoneNumber = string
Sedan kan du använda aliaset precis som alla andra typer:
// Create a record type with the alias
type Contact = {
Name : Name
Phone : PhoneNumber }
// Create a record instance
// We can assign a string since Name and PhoneNumber are just aliases on string type
let c = {
Name = "Foo"
Phone = "00 000 000" }
printfn "%A" c
// Output
// {Name = "Foo";
// Phone = "00 000 000";}
Var försiktig, alias kontrollerar inte om det är typisk konsistens. Detta innebär att två alias som riktar sig till samma typ kan tilldelas varandra:
let c = {
Name = "Foo"
Phone = "00 000 000" }
let d = {
Name = c.Phone
Phone = c.Name }
printfn "%A" d
// Output
// {Name = "00 000 000";
// Phone = "Foo";}
Typer skapas i F # med hjälp av typsökord
F#
använder type
sökord för att skapa olika typer av slag.
- Skriv alias
- Diskriminerade facktyper
- Spela in typer
- Gränssnittstyper
- Klasstyper
- Strukturtyper
Exempel med motsvarande C#
-kod där det är möjligt:
// Equivalent C#:
// using IntAliasType = System.Int32;
type IntAliasType = int // As in C# this doesn't create a new type, merely an alias
type DiscriminatedUnionType =
| FirstCase
| SecondCase of int*string
member x.SomeProperty = // We can add members to DU:s
match x with
| FirstCase -> 0
| SecondCase (i, _) -> i
type RecordType =
{
Id : int
Name : string
}
static member New id name : RecordType = // We can add members to records
{ Id = id; Name = name } // { ... } syntax used to create records
// Equivalent C#:
// interface InterfaceType
// {
// int Id { get; }
// string Name { get; }
// int Increment (int i);
// }
type InterfaceType =
interface // In order to create an interface type, can also use [<Interface>] attribute
abstract member Id : int
abstract member Name : string
abstract member Increment : int -> int
end
// Equivalent C#:
// class ClassType : InterfaceType
// {
// static int increment (int i)
// {
// return i + 1;
// }
//
// public ClassType (int id, string name)
// {
// Id = id ;
// Name = name ;
// }
//
// public int Id { get; private set; }
// public string Name { get; private set; }
// public int Increment (int i)
// {
// return increment (i);
// }
// }
type ClassType (id : int, name : string) = // a class type requires a primary constructor
let increment i = i + 1 // Private helper functions
interface InterfaceType with // Implements InterfaceType
member x.Id = id
member x.Name = name
member x.Increment i = increment i
// Equivalent C#:
// class SubClassType : ClassType
// {
// public SubClassType (int id, string name) : base(id, name)
// {
// }
// }
type SubClassType (id : int, name : string) =
inherit ClassType (id, name) // Inherits ClassType
// Equivalent C#:
// struct StructType
// {
// public StructType (int id)
// {
// Id = id;
// }
//
// public int Id { get; private set; }
// }
type StructType (id : int) =
struct // In order create a struct type, can also use [<Struct>] attribute
member x.Id = id
end
Skriv inferens
Bekräftelse
Detta exempel är anpassat från denna artikel om typinferens
Vad är typinferens?
Typinferens är den mekanism som gör det möjligt för kompilatorn att härleda vilka typer som används och var. Denna mekanism är baserad på en algoritm som ofta kallas "Hindley-Milner" eller "HM". Se nedan några regler för att bestämma typerna av enkla värden och funktionsvärden:
- Titta på bokstäverna
- Titta på funktionerna och andra värden som något interagerar med
- Titta på eventuella specifika begränsningar
- Om det inte finns några begränsningar någonstans, generaliseras automatiskt till generiska typer
Titta på bokstäverna
Kompilatorn kan härleda typer genom att titta på bokstäverna. Om det bokstavliga är ett int och du lägger till "x" till det, måste "x" också vara ett int. Men om det bokstavliga är en flottör och du lägger till "x" till det, måste "x" också vara en flottör.
Här är några exempel:
let inferInt x = x + 1
let inferFloat x = x + 1.0
let inferDecimal x = x + 1m // m suffix means decimal
let inferSByte x = x + 1y // y suffix means signed byte
let inferChar x = x + 'a' // a char
let inferString x = x + "my string"
Titta på funktionerna och andra värden som den interagerar med
Om det inte finns några bokstäver någonstans, försöker kompilatorn räkna ut typerna genom att analysera funktionerna och andra värden som de interagerar med.
let inferInt x = x + 1
let inferIndirectInt x = inferInt x //deduce that x is an int
let inferFloat x = x + 1.0
let inferIndirectFloat x = inferFloat x //deduce that x is a float
let x = 1
let y = x //deduce that y is also an int
Titta på eventuella uttryckliga begränsningar eller kommentarer
Om det finns några specifika begränsningar eller kommentarer som anges, kommer kompilatorn att använda dem.
let inferInt2 (x:int) = x // Take int as parameter
let inferIndirectInt2 x = inferInt2 x // Deduce from previous that x is int
let inferFloat2 (x:float) = x // Take float as parameter
let inferIndirectFloat2 x = inferFloat2 x // Deduce from previous that x is float
Automatisk generalisering
Om det efter allt detta inte finns några begränsningar, gör kompilatorn bara typerna generiska.
let inferGeneric x = x
let inferIndirectGeneric x = inferGeneric x
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString()
Saker som kan gå fel med slutsatsen
Typinferensen är inte perfekt, tyvärr. Ibland har kompilatorn bara ingen aning om vad man ska göra. Återigen kommer att förstå vad som händer verkligen hjälpa dig att hålla dig lugn istället för att vilja döda kompilatorn. Här är några av de främsta orsakerna till typfel:
- Förklaringar som inte är i ordning
- Inte tillräckligt med information
- Överbelastade metoder
Förklaringar som inte är i ordning
En grundregel är att du måste deklarera funktioner innan de används.
Den här koden misslyckas:
let square2 x = square x // fails: square not defined
let square x = x * x
Men det här är ok:
let square x = x * x
let square2 x = square x // square already defined earlier
Rekursiva eller samtidiga förklaringar
En variant av "out of order" -problemet uppstår med rekursiva funktioner eller definitioner som måste hänvisa till varandra. Ingen mängd ombeställning hjälper i detta fall - vi måste använda ytterligare nyckelord för att hjälpa kompilatorn.
När en funktion sammanställs är funktionsidentifieraren inte tillgänglig för kroppen. Så om du definierar en enkel rekursiv funktion får du ett kompileringsfel. Fixet är att lägga till nyckelordet "rec" som en del av funktionsdefinitionen. Till exempel:
// the compiler does not know what "fib" means
let fib n =
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
// error FS0039: The value or constructor 'fib' is not defined
Här är den fasta versionen med "rec fib" tillagd för att indikera att den är rekursiv:
let rec fib n = // LET REC rather than LET
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
Inte tillräckligt med information
Ibland har kompilatorn bara inte tillräckligt med information för att bestämma en typ. I följande exempel vet kompilatorn inte vilken typ Längdmetoden ska fungera på. Men det kan inte göra det generiskt heller, så det klagar.
let stringLength s = s.Length
// error FS0072: Lookup on object of indeterminate type
// based on information prior to this program point.
// A type annotation may be needed ...
Sådana fel kan åtgärdas med uttryckliga kommentarer.
let stringLength (s:string) = s.Length
Överbelastade metoder
När du ringer en extern klass eller metod i .NET får du ofta fel på grund av överbelastning.
I många fall, till exempel concat-exemplet nedan, måste du uttryckligen kommentera parametrarna för den externa funktionen så att kompilatorn vet vilken överbelastad metod att ringa.
let concat x = System.String.Concat(x) //fails
let concat (x:string) = System.String.Concat(x) //works
let concat x = System.String.Concat(x:string) //works
Ibland har de överbelastade metoderna olika argumentnamn, i vilket fall kan du också ge kompilatorn en ledtråd genom att namnge argumenten. Här är ett exempel på StreamReader-konstruktören.
let makeStreamReader x = new System.IO.StreamReader(x) //fails
let makeStreamReader x = new System.IO.StreamReader(path=x) //works