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.

  1. Skriv alias
  2. Diskriminerade facktyper
  3. Spela in typer
  4. Gränssnittstyper
  5. Klasstyper
  6. 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


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow