Generalizzazione automatica

F# usa l'inferenza dei tipi per valutare i tipi di funzioni ed espressioni. In questo argomento viene descritto come F# generalizza automaticamente gli argomenti e i tipi di funzioni in modo che funzionino con più tipi quando possibile.

Generalizzazione automatica

Il compilatore F#, quando esegue l'inferenza dei tipi in una funzione, determina se un determinato parametro può essere generico. Il compilatore esamina ogni parametro e determina se la funzione ha una dipendenza dal tipo specifico di tale parametro. In caso contrario, il tipo viene dedotto come generico.

Nell'esempio di codice seguente viene illustrata una funzione inferta dal compilatore per essere generica.

let max a b = if a > b then a else b

Il tipo viene dedotto come 'a -> 'a -> 'a.

Il tipo indica che si tratta di una funzione che accetta due argomenti dello stesso tipo sconosciuto e restituisce un valore dello stesso tipo. Uno dei motivi per cui la funzione precedente può essere generica è che l'operatore maggiore di (>) è generico. L'operatore greater-than ha la firma 'a -> 'a -> bool. Non tutti gli operatori sono generici e se il codice in una funzione usa un tipo di parametro insieme a una funzione o a un operatore non generico, tale tipo di parametro non può essere generalizzato.

Poiché max è generico, può essere usato con tipi come int, floate così via, come illustrato negli esempi seguenti.

let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3

Tuttavia, i due argomenti devono essere dello stesso tipo. La firma è 'a -> 'a -> 'a, non 'a -> 'b -> 'a. Di conseguenza, il codice seguente genera un errore perché i tipi non corrispondono.

// Error: type mismatch.
let biggestIntFloat = max 2.0 3

La max funzione funziona anche con qualsiasi tipo che supporti l'operatore maggiore di. Pertanto, è anche possibile usarlo in una stringa, come illustrato nel codice seguente.

let testString = max "cab" "cat"

Restrizione dei valori

Il compilatore esegue la generalizzazione automatica solo sulle definizioni di funzione complete con argomenti espliciti e su semplici valori non modificabili.

Ciò significa che il compilatore genera un errore se si tenta di compilare codice che non è sufficientemente vincolato per essere un tipo specifico, ma non è anche generalizzabile. Il messaggio di errore per questo problema si riferisce a questa restrizione sulla generalizzazione automatica per i valori come restrizione del valore.

In genere, l'errore di restrizione del valore si verifica quando si vuole che un costrutto sia generico, ma il compilatore non dispone di informazioni sufficienti per generalizzarlo o quando si omettono involontariamente informazioni di tipo sufficienti in un costrutto non generico. La soluzione all'errore di restrizione del valore consiste nel fornire informazioni più esplicite per limitare più completamente il problema di inferenza del tipo, in uno dei modi seguenti:

  • Vincolare un tipo per essere non generico aggiungendo un'annotazione di tipo esplicita a un valore o a un parametro.

  • Se il problema usa un costrutto non generalizzabile per definire una funzione generica, ad esempio una composizione di funzione o argomenti di funzione curried applicati in modo incompleto, provare a riscrivere la funzione come definizione di funzione normale.

  • Se il problema è un'espressione troppo complessa da generalizzare, impostarla in una funzione aggiungendo un parametro aggiuntivo inutilizzato.

  • Aggiungere parametri di tipo generico espliciti. Questa opzione viene usata raramente.

Gli esempi di codice seguenti illustrano ognuno di questi scenari.

Caso 1: troppo complessa un'espressione. In questo esempio l'elenco counter deve essere int option ref, ma non è definito come un semplice valore non modificabile.

let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None

Caso 2: uso di un costrutto non generalizzabile per definire una funzione generica. In questo esempio il costrutto non è generabile perché comporta un'applicazione parziale di argomenti di funzione.

let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj

Caso 3: Aggiunta di un parametro aggiuntivo inutilizzato. Poiché questa espressione non è abbastanza semplice per la generalizzazione, il compilatore rilascia l'errore di restrizione del valore.

let emptyList10 = Array.create 10 []
// Adding an extra (unused) parameter makes it a function, which is generalizable.
let emptyList10 () = Array.create 10 []

Caso 4: Aggiunta di parametri di tipo.

let arrayOf10Lists = Array.create 10 []
// Adding a type parameter and type annotation lets you write a generic value.
let arrayOf10Lists<'T> = Array.create 10 ([]:'T list)

Nell'ultimo caso, il valore diventa una funzione di tipo, che può essere usata per creare valori di molti tipi diversi, ad esempio come indicato di seguito:

let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>

Vedi anche