Generalizzazione automatica (F#)
In F# viene utilizzata l'inferenza del tipo per valutare i tipi di funzioni ed espressioni. In questo argomento viene descritto in che modo in F# vengono generalizzati automaticamente gli argomenti e i tipi di funzioni, in modo da poter essere utilizzati con più tipi, quando possibile.
Generalizzazione automatica
Se esegue l'inferenza del tipo in una funzione, il compilatore F# determina se un particolare parametro può essere generico. Il compilatore esamina ogni parametro e determina se la funzione prevede una dipendenza dal tipo specifico di tale parametro. In caso contrario, il tipo viene considerato come generico.
Nell'esempio di codice seguente viene illustrata una funzione considerata dal compilatore come generica.
let max a b = if a > b then a else b
Il tipo viene derivato come 'a -> 'a -> 'a.
Il tipo indica che questa funzione 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 maggiore di ha la firma 'a -> 'a -> bool. Non tutti gli operatori sono generici e se il codice in una funzione utilizza 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 utilizzato con tipi quali int, float e così via, come indicato negli esempi seguenti.
let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3
I due argomenti devono tuttavia essere dello stesso tipo. La firma è 'a -> 'a -> 'a, non 'a -> 'b -> 'a. Dal codice seguente viene pertanto generato un errore, poiché i tipi non corrispondono.
// Error: type mismatch.
let biggestIntFloat = max 2.0 3
La funzione max è inoltre compatibile con qualsiasi tipo che supporta l'operatore greater-than. È quindi possibile utilizzarla in una stringa, come indicato nel codice seguente.
let testString = max "cab" "cat"
Restrizione dei valori
Il compilatore esegue la generalizzazione automatica solo nelle definizioni di funzioni complete che dispongono di argomenti espliciti e nei valori semplici non modificabili.
Questo significa che il compilatore genera un errore se si tenta di compilare codice non sufficientemente vincolato a un tipo specifico, ma anche non generalizzabile. Il messaggio di errore per questo problema fa riferimento a questa restrizione sulla generalizzazione automatica dei valori come a una restrizione dei valori.
In genere, l'errore di restrizione dei valori si verifica quando si desidera che un costrutto sia generico ma il compilatore dispone di informazioni insufficienti per generalizzarlo o quando si omettono in modo involontario informazioni sul tipo sufficienti in un costrutto non generico. La soluzione all'errore di restrizione dei valori consiste nel fornire ulteriori informazioni esplicite per vincolare in modo più completo il problema dell'inferenza del tipo, in uno dei modi seguenti:
Vincolare un tipo in modo che sia non generico aggiungendo un'annotazione del tipo esplicita a un valore o a un parametro.
Se il problema è dovuto all'utilizzo di un costrutto non generalizzabile per definire una funzione generica, ad esempio una composizione di funzione o argomenti di funzioni sottoposte a currying applicati in modo non completo, provare a riscrivere la funzione come definizione di funzione comune.
Se il problema è dovuto a un'espressione troppo complessa per essere generalizzata, trasformarla in una funzione aggiungendo un ulteriore parametro inutilizzato.
Aggiungere parametri di tipo generico espliciti. Questa opzione viene utilizzata raramente.
Negli esempi di codice seguenti vengono illustrati questi scenari.
Caso 1: espressione troppo complessa. In questo esempio l'elenco counter deve essere di tipo int option ref, ma non viene definito come valore semplice non modificabile.
let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None
Caso 2: utilizzo di un costrutto non generalizzabile per definire una funzione generica. In questo esempio il costrutto è non generalizzabile, poiché coinvolge l'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 ulteriore parametro inutilizzato. Poiché l'espressione non è sufficientemente semplice per la generalizzazione, il compilatore pubblica 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 utilizzata per creare valori di molti tipi diversi, ad esempio come segue:
let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>