Condividi tramite


Generici

I valori delle funzioni F#, i metodi, le proprietà e i tipi di aggregazione, ad esempio classi, record e unioni discriminate, possono essere generici. I costrutti generici contengono almeno un parametro di tipo, in genere fornito dall'utente del costrutto generico. Le funzioni e i tipi generici consentono di scrivere codice che funziona con un'ampia gamma di tipi senza ripetere il codice per ogni tipo. Rendere generico il codice può essere semplice in F#, perché spesso il codice viene dedotto in modo implicito per essere generico dall'inferenza del tipo del compilatore e dai meccanismi di generalizzazione automatica.

Sintassi

// Explicitly generic function.
let function-name<type-parameters> parameter-list =
function-body

// Explicitly generic method.
[ static ] member object-identifier.method-name<type-parameters> parameter-list [ return-type ] =
method-body

// Explicitly generic class, record, interface, structure,
// or discriminated union.
type type-name<type-parameters> type-definition

Osservazioni:

La dichiarazione di una funzione o di un tipo generico in modo esplicito è molto simile a quella di una funzione o di un tipo non generico, ad eccezione della specifica (e dell'uso) dei parametri di tipo, tra parentesi angolari dopo la funzione o il nome del tipo.

Le dichiarazioni sono spesso generiche in modo implicito. Se non si specifica completamente il tipo di ogni parametro usato per comporre una funzione o un tipo, il compilatore tenta di dedurre il tipo di ogni parametro, valore e variabile dal codice scritto. Per altre informazioni, vedere Inferenza dei tipi. Se il codice per il tipo o la funzione non vincola altrimenti i tipi di parametri, la funzione o il tipo è implicitamente generico. Questo processo è denominato generalizzazione automatica. Esistono alcuni limiti alla generalizzazione automatica. Ad esempio, se il compilatore F# non è in grado di dedurre i tipi per un costrutto generico, il compilatore segnala un errore che fa riferimento a una restrizione denominata restrizione del valore. In tal caso, potrebbe essere necessario aggiungere alcune annotazioni di tipo. Per altre informazioni sulla generalizzazione automatica e sulla restrizione del valore e su come modificare il codice per risolvere il problema, vedere Generalizzazione automatica.

Nella sintassi precedente, i parametri di tipo sono un elenco delimitato da virgole di parametri che rappresentano tipi sconosciuti, ognuno dei quali inizia con una virgoletta singola, facoltativamente con una clausola di vincolo che limita ulteriormente i tipi che possono essere usati per tale parametro di tipo. Per la sintassi per le clausole di vincolo di vari tipi e altre informazioni sui vincoli, vedere Vincoli.

La definizione di tipo nella sintassi corrisponde alla definizione del tipo per un tipo non generico. Include i parametri del costruttore per un tipo di classe, una clausola facoltativa as , il simbolo uguale, i campi del record, la inherit clausola , le scelte per un'unione let discriminata e do le associazioni, le definizioni dei membri e qualsiasi altro elemento consentito in una definizione di tipo non generico.

Gli altri elementi della sintassi sono uguali a quelli per funzioni e tipi non generici. Ad esempio, object-identifier è un identificatore che rappresenta l'oggetto contenitore stesso.

Le proprietà, i campi e i costruttori non possono essere più generici del tipo di inclusione. Inoltre, i valori in un modulo non possono essere generici.

Costrutti generici in modo implicito

Quando il compilatore F# deduce i tipi nel codice, considera automaticamente qualsiasi funzione che può essere generica. Se si specifica un tipo in modo esplicito, ad esempio un tipo di parametro, si impedisce la generalizzazione automatica.

Nell'esempio di codice seguente, makeList è generico, anche se né esso né i relativi parametri vengono dichiarati in modo esplicito come generico.

let makeList a b = [ a; b ]

La firma della funzione viene dedotta come 'a -> 'a -> 'a list. Si noti che a e b in questo esempio vengono dedotti per avere lo stesso tipo. Ciò è dovuto al fatto che sono inclusi in un elenco insieme e tutti gli elementi di un elenco devono essere dello stesso tipo.

È anche possibile rendere generica una funzione usando la sintassi delle virgolette singole in un'annotazione di tipo per indicare che un tipo di parametro è un parametro di tipo generico. Nel codice seguente, function1 è generico perché i relativi parametri vengono dichiarati in questo modo, come parametri di tipo.

let function1 (x: 'a) (y: 'a) = printfn "%A %A" x y

Costrutti generici in modo esplicito

È anche possibile rendere una funzione generica dichiarando in modo esplicito i relativi parametri di tipo tra parentesi angolari (<type-parameter>). Il codice seguente illustra questa operazione.

let function2<'T> (x: 'T) (y: 'T) = printfn "%A, %A" x y

Uso di costrutti generici

Quando si usano funzioni o metodi generici, potrebbe non essere necessario specificare gli argomenti di tipo. Il compilatore usa l'inferenza del tipo per dedurre gli argomenti di tipo appropriati. Se esiste ancora un'ambiguità, è possibile specificare argomenti di tipo tra parentesi angolari, separando più argomenti di tipo con virgole.

Il codice seguente illustra l'uso delle funzioni definite nelle sezioni precedenti.

// In this case, the type argument is inferred to be int.
function1 10 20
// In this case, the type argument is float.
function1 10.0 20.0
// Type arguments can be specified, but should only be specified
// if the type parameters are declared explicitly. If specified,
// they have an effect on type inference, so in this example,
// a and b are inferred to have type int.
let function3 a b =
    // The compiler reports a warning:
    function1<int> a b
    // No warning.
    function2<int> a b

Annotazioni

Esistono due modi per fare riferimento a un tipo generico in base al nome. Ad esempio, list<int> e int list sono due modi per fare riferimento a un tipo generico con un singolo argomento intdi list tipo . Quest'ultima forma viene usata convenzionalmente solo con tipi F# predefiniti, ad list esempio e option. Se sono presenti più argomenti di tipo, in genere si usa la sintassi Dictionary<int, string> , ma è anche possibile usare la sintassi (int, string) Dictionary.

Caratteri jolly come argomenti di tipo

Per specificare che un argomento di tipo deve essere dedotto dal compilatore, è possibile usare il carattere di sottolineatura o il simbolo jolly (_), anziché un argomento di tipo denominato. Questo è illustrato nel codice seguente.

let printSequence (sequence1: Collections.seq<_>) =
    Seq.iter (fun elem -> printf "%s " (elem.ToString())) sequence1

Vincoli in tipi e funzioni generici

In un tipo generico o in una definizione di funzione è possibile usare solo i costrutti noti per essere disponibili nel parametro di tipo generico. Questa operazione è necessaria per abilitare la verifica delle chiamate di funzione e metodo in fase di compilazione. Se si dichiarano i parametri di tipo in modo esplicito, è possibile applicare un vincolo esplicito a un parametro di tipo generico per notificare al compilatore che sono disponibili determinati metodi e funzioni. Tuttavia, se si consente al compilatore F# di dedurre i tipi di parametri generici, determinerà automaticamente i vincoli appropriati. Per altre informazioni, vedere Vincoli.

Parametri di tipo risolti in modo statico

Esistono due tipi di parametri di tipo che possono essere usati nei programmi F#. Il primo sono parametri di tipo generico del tipo descritto nelle sezioni precedenti. Questo primo tipo di parametro di tipo è equivalente ai parametri di tipo generici usati nei linguaggi come Visual Basic e C#. Un altro tipo di parametro di tipo è specifico di F# e viene definito parametro di tipo risolto in modo statico. Per informazioni su questi costrutti, vedere Parametri di tipo risolti in modo statico.

Esempi

// A generic function.
// In this example, the generic type parameter 'a makes function3 generic.
let function3 (x: 'a) (y: 'a) = printf "%A %A" x y

// A generic record, with the type parameter in angle brackets.
type GR<'a> = { Field1: 'a; Field2: 'a }

// A generic class.
type C<'a>(a: 'a, b: 'a) =
    let z = a
    let y = b
    member this.GenericMethod(x: 'a) = printfn "%A %A %A" x y z

// A generic discriminated union.
type U<'a> =
    | Choice1 of 'a
    | Choice2 of 'a * 'a

type Test() =
    // A generic member
    member this.Function1<'a>(x, y) = printfn "%A, %A" x y

    // A generic abstract method.
    abstract abstractMethod<'a, 'b> : 'a * 'b -> unit
    override this.abstractMethod<'a, 'b>(x: 'a, y: 'b) = printfn "%A, %A" x y

Vedere anche