Zoeken…


Inleiding tot typen

Typen kunnen verschillende soorten dingen vertegenwoordigen. Het kan een enkele data, een set data of een functie zijn.

In F # kunnen we de typen in twee categorieën groeperen:

  • F # types:

     // 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-typen

    • Ingebouwd type (int, bool, string, ...)
    • Klassen, structuren en interfaces
    • afgevaardigden
    • arrays

Type afkortingen

Met typeafkortingen kunt u aliassen maken op bestaande typen om ze een betekenisvollere betekenis te geven.

// Name is an alias for a string
type Name = string

// PhoneNumber is an alias for a string
type PhoneNumber = string

Dan kunt u de alias net als elk ander type gebruiken:

// 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";}

Wees voorzichtig, aliassen controleren niet op typeconsistentie. Dit betekent dat twee aliassen die op hetzelfde type zijn gericht, aan elkaar kunnen worden toegewezen:

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";}

Typen worden gemaakt in F # met behulp van het trefwoord type

F# gebruikt het sleutelwoord type om verschillende soorten typen te maken.

  1. Typ aliassen
  2. Discriminatie van unietypen
  3. Recordtypen
  4. Interface types
  5. Klasse typen
  6. Struct types

Voorbeelden met gelijkwaardige C# -code waar mogelijk:

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

Type Inferentie

Erkenning

Dit voorbeeld is overgenomen uit dit artikel over type-inferentie

Wat is type inferentie?

Type Inferentie is het mechanisme waarmee de compiler kan afleiden welke typen worden gebruikt en waar. Dit mechanisme is gebaseerd op een algoritme dat vaak "Hindley-Milner" of "HM" wordt genoemd. Zie hieronder enkele regels voor het bepalen van de soorten eenvoudige en functiewaarden:

  • Kijk naar de letterlijke dingen
  • Kijk naar de functies en andere waarden waar iets mee interageert
  • Bekijk eventuele expliciete typebeperkingen
  • Als er nergens beperkingen zijn, generaliseer dan automatisch naar generieke typen

Kijk naar de letterlijke dingen

De compiler kan typen afleiden door naar de letterlijke waarden te kijken. Als het letterlijke een int is en u voegt "x" eraan toe, dan moet "x" ook een int zijn. Maar als het letterlijke een float is en u voegt "x" eraan toe, dan moet "x" ook een float zijn.

Hier zijn enkele voorbeelden:

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"

Bekijk de functies en andere waarden waarmee het samenwerkt

Als er nergens letterlijk is, probeert de compiler de typen uit te werken door de functies en andere waarden te analyseren waarmee ze communiceren.

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

Bekijk expliciete typebeperkingen of annotaties

Als er expliciete typebeperkingen of annotaties zijn opgegeven, gebruikt de compiler deze.

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

Automatische generalisatie

Als er na dit alles geen beperkingen worden gevonden, maakt de compiler alleen de typen generiek.

let inferGeneric x = x 
let inferIndirectGeneric x = inferGeneric x 
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString() 

Dingen die mis kunnen gaan met type gevolgtrekking

De type inferentie is helaas niet perfect. Soms heeft de compiler gewoon geen idee wat hij moet doen. Nogmaals, als je begrijpt wat er gebeurt, zul je echt kalm blijven in plaats van de compiler te willen doden. Hier zijn enkele van de belangrijkste redenen voor typefouten:

  • Verklaringen buiten gebruik
  • Niet genoeg informatie
  • Overbelaste methoden

Verklaringen buiten gebruik

Een basisregel is dat u functies moet declareren voordat ze worden gebruikt.

Deze code mislukt:

let square2 x = square x   // fails: square not defined
let square x = x * x

Maar dit is goed:

let square x = x * x       
let square2 x = square x   // square already defined earlier

Recursieve of gelijktijdige verklaringen

Een variant van het probleem 'buiten werking' treedt op bij recursieve functies of definities die naar elkaar moeten verwijzen. In dit geval helpt geen enkele herschikking - we moeten extra trefwoorden gebruiken om de compiler te helpen.

Wanneer een functie wordt gecompileerd, is de functie-ID niet beschikbaar voor het lichaam. Dus als u een eenvoudige recursieve functie definieert, krijgt u een compilerfout. De oplossing is om het trefwoord "rec" toe te voegen als onderdeel van de functiedefinitie. Bijvoorbeeld:

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

Hier is de vaste versie met "rec fib" toegevoegd om aan te geven dat het recursief is:

let rec fib n =              // LET REC rather than LET
   if n <= 2 then 1
   else fib (n - 1) + fib (n - 2)

Niet genoeg informatie

Soms heeft de compiler gewoon niet genoeg informatie om een type te bepalen. In het volgende voorbeeld weet de compiler niet op welk type de lengtemethode zou moeten werken. Maar het kan het ook niet generiek maken, dus klaagt het.

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

Dit soort fouten kan worden opgelost met expliciete annotaties.

let stringLength (s:string) = s.Length

Overbelaste methoden

Wanneer u een externe klasse of methode in .NET aanroept, krijgt u vaak fouten als gevolg van overbelasting.

In veel gevallen, zoals het onderstaande concat-voorbeeld, moet u de parameters van de externe functie expliciet annoteren, zodat de compiler weet welke overbelaste methode moet worden aangeroepen.

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

Soms hebben de overbelaste methoden verschillende argumentnamen, in welk geval u de compiler ook een idee kunt geven door de argumenten een naam te geven. Hier is een voorbeeld voor de StreamReader-constructor.

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow