Ricerca…


Introduzione ai tipi

I tipi possono rappresentare vari tipi di cose. Può essere un singolo dato, un insieme di dati o una funzione.

In F #, possiamo raggruppare i tipi in due categorie .:

  • Tipi F #:

     // 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
    
  • Tipi .NET

    • Tipo integrato (int, bool, stringa, ...)
    • Classi, strutture e interfacce
    • I delegati
    • Array

Abbreviazioni di tipo

Le abbreviazioni di tipo ti consentono di creare alias su tipi esistenti per dare loro un significato più significativo.

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

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

Quindi puoi usare l'alias come qualsiasi altro tipo:

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

Fai attenzione, gli alias non controllano la coerenza del tipo. Ciò significa che due alias che hanno come target lo stesso tipo possono essere assegnati l'uno all'altro:

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

I tipi sono creati in F # usando la parola chiave type

F# usa la parola chiave type per creare diversi tipi di tipi.

  1. Digita gli alias
  2. Tipi sindacali discriminati
  3. Registra i tipi
  4. Tipi di interfaccia
  5. Tipi di classe
  6. Tipi di Struct

Esempi con codice C# equivalente C# ove possibile:

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

Tipo di inferenza

Riconoscimento

Questo esempio è adattato da questo articolo sull'inferenza di tipo

Cos'è il tipo Inferenza?

Type Inference è il meccanismo che consente al compilatore di dedurre quali tipi sono usati e dove. Questo meccanismo è basato su un algoritmo spesso chiamato "Hindley-Milner" o "HM". Vedi sotto alcune delle regole per determinare i tipi di valori semplici e funzionali:

  • Guarda i letterali
  • Guarda le funzioni e gli altri valori con cui qualcosa interagisce
  • Guarda qualsiasi vincolo di tipo esplicito
  • Se non ci sono vincoli da nessuna parte, generalizza automaticamente a tipi generici

Guarda i letterali

Il compilatore può dedurre i tipi osservando i letterali. Se il letterale è un int e si aggiunge "x" ad esso, quindi "x" deve essere anche un int. Ma se il letterale è un float e stai aggiungendo "x" ad esso, anche "x" deve essere un float.

Ecco alcuni esempi:

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"

Guarda le funzioni e gli altri valori con cui interagisce

Se non ci sono letterali da nessuna parte, il compilatore prova ad elaborare i tipi analizzando le funzioni e gli altri valori con cui interagiscono.

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

Guarda qualsiasi vincolo o annotazione di tipo esplicito

Se sono specificati vincoli di tipo esplicito o annotazioni, il compilatore li utilizzerà.

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

Generalizzazione automatica

Se dopo tutto questo, non sono stati trovati vincoli, il compilatore rende semplicemente i tipi generici.

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

Cose che possono andare storte con l'inferenza di tipo

L'inferenza di tipo non è perfetta, ahimè. A volte il compilatore non ha la minima idea di cosa fare. Di nuovo, capire cosa sta succedendo ti aiuterà davvero a stare calmo invece di voler uccidere il compilatore. Ecco alcuni dei principali motivi per gli errori di tipo:

  • Dichiarazioni fuori servizio
  • Non abbastanza informazioni
  • Metodi sovraccaricati

Dichiarazioni fuori servizio

Una regola di base è che devi dichiarare le funzioni prima che vengano utilizzate.

Questo codice ha esito negativo:

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

Ma questo è ok:

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

Dichiarazioni ricorsive o simultanee

Una variante del problema "fuori servizio" si verifica con funzioni o definizioni ricorsive che devono fare riferimento l'una all'altra. In questo caso nessuna quantità di riordino sarà d'aiuto: dobbiamo usare parole chiave aggiuntive per aiutare il compilatore.

Quando una funzione viene compilata, l'identificatore della funzione non è disponibile per il corpo. Quindi, se si definisce una funzione ricorsiva semplice, si otterrà un errore del compilatore. La correzione consiste nell'aggiungere la parola chiave "rec" come parte della definizione della funzione. Per esempio:

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

Ecco la versione fissa con "rec fib" aggiunta per indicare che è ricorsiva:

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

Non abbastanza informazioni

A volte, il compilatore non ha abbastanza informazioni per determinare un tipo. Nell'esempio seguente, il compilatore non conosce il tipo su cui il metodo Length deve funzionare. Ma non può nemmeno renderlo generico, quindi si lamenta.

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

Questi tipi di errore possono essere corretti con annotazioni esplicite.

let stringLength (s:string) = s.Length

Metodi sovraccaricati

Quando si chiama una classe o un metodo esterno in .NET, si verificano spesso errori dovuti a sovraccarico.

In molti casi, come nell'esempio di concat qui sotto, dovresti annotare esplicitamente i parametri della funzione esterna in modo che il compilatore sappia quale metodo di overload debba chiamare.

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

A volte i metodi in overload hanno nomi di argomenti diversi, nel qual caso puoi anche dare al compilatore un indizio nominando gli argomenti. Ecco un esempio per il costruttore StreamReader.

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow