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.
- Digita gli alias
- Tipi sindacali discriminati
- Registra i tipi
- Tipi di interfaccia
- Tipi di classe
- 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