Parametri di tipo risolti staticamente

Un parametro di tipo staticamente risolto è un parametro di tipo che viene sostituito con un tipo effettivo in fase di compilazione anziché in fase di esecuzione.

Sintassi

'type-parameter

Fino alla versione 7.0 di F#, era necessario usare la sintassi seguente

^type-parameter

Osservazioni:

In F#, esistono due tipi distinti di parametri di tipo. Il primo è il parametro di tipo generico standard. Si tratta di un parametro equivalente a quelli di tipo generico presenti in altri linguaggi .NET. L'altro tipo è staticamente risolto e può essere usato solo nelle funzioni inline.

I parametri di tipo staticamente risolto sono utili soprattutto in combinazione con i vincoli di membro, che consentono di specificare che un argomento di tipo deve disporre di uno o più membri specifici per poter essere usato. Non è possibile creare questo tipo di vincolo usando un normale parametro di tipo generico.

La tabella seguente riepiloga le analogie e le differenze tra i due tipi di parametri di tipo.

Funzionalità Generica Staticamente risolto
Tempo risoluzione Tempo di esecuzione Fase di compilazione
Vincoli di membro Non può essere usato con vincoli di membro. Può essere usato con vincoli di membro.
Generazione codice Un tipo o un metodo con parametri di tipo generico standard genera un singolo tipo o metodo generico. Vengono generate istanziazioni multiple di tipi e metodi, una per ogni tipo necessario.
Utilizzo con i tipi Può essere usato nei tipi. Non può essere usato nei tipi.
Utilizzo con le funzioni inline Una funzione inline non può essere parametrizzata con un parametro di tipo generico standard. Se gli input non sono completamente generici, il compilatore F# li specializza o, se non sono disponibili opzioni da specializzare, restituisce un errore. I parametri di tipo staticamente risolto non possono essere usati in funzioni o metodi non inline.

Molte funzioni della libreria principale F#, in particolare gli operatori, dispongono di parametri di tipo staticamente risolto. Queste funzioni e operatori sono inline e consentono di generare codice efficiente per i calcoli numerici.

I metodi e le funzioni inline che usano gli operatori o altre funzioni che dispongono di parametri di tipo staticamente risolto, a loro volta possono anche usare tali parametri. L'inferenza dei tipi spesso deduce da tali funzioni inline l'esistenza di parametri di tipo staticamente risolto. Nell'esempio seguente viene illustrata la definizione di un operatore che viene dedotta dall'esistenza di un parametro di tipo staticamente risolto.

let inline (+@) x y = x + x * y
// Call that uses int.
printfn "%d" (1 +@ 1)
// Call that uses float.
printfn "%f" (1.0 +@ 0.5)

Il tipo risolto di (+@) si basa sull'uso di (+) e (*), ciascuno dei quali causa l'inferenza dei tipi per dedurre i vincoli dei membri nei parametri di tipo staticamente risolto. Il tipo risolto, come illustrato nell'interprete F#, è il seguente.

'a -> 'c -> 'd
when ('a or 'b) : (static member ( + ) : 'a * 'b -> 'd) and
('a or 'c) : (static member ( * ) : 'a * 'c -> 'b)

L'output è indicato di seguito.

2
1.500000

L'esempio seguente illustra l'utilizzo di SRTP con metodi e metodi statici:

type Record =
    { Number: int }
    member this.Double() = { Number = this.Number * 2 }
    static member Zero() = { Number = 0 }
    
let inline double<'a when 'a:(member Double: unit -> 'a)> (x: 'a) = x.Double()    
let inline zero<'a when 'a:(static member Zero: unit -> 'a)> () = 'a.Zero()

let r: Record = zero ()
let doubleR = double r

A partire dalla versione F# 7.0, è possibile usare 'a.Zero() anziché ripetere il vincolo, come nell'esempio seguente.

A partire dalla versione F# 4.1, è inoltre possibile specificare nomi di tipi concreti nelle firme dei parametri di tipo staticamente risolto. Nelle versioni precedenti del linguaggio, il nome del tipo veniva dedotto dal compilatore, ma non poteva essere specificato nella firma. A partire dalla versione F# 4.1, è inoltre possibile specificare nomi di tipi concreti nelle firme dei parametri di tipo staticamente risolto. Di seguito è riportato un esempio, in cui è necessario usare ^, poiché il metodo della semplificazione per l’uso di ' non è supportato:

let inline konst x _ = x

type CFunctor() =
    static member inline fmap (f: ^a -> ^b, a: ^a list) = List.map f a
    static member inline fmap (f: ^a -> ^b, a: ^a option) =
        match a with
        | None -> None
        | Some x -> Some (f x)

    // default implementation of replace
    static member inline replace< ^a, ^b, ^c, ^d, ^e when ^a :> CFunctor and (^a or ^d): (static member fmap: (^b -> ^c) * ^d -> ^e) > (a, f) =
        ((^a or ^d) : (static member fmap : (^b -> ^c) * ^d -> ^e) (konst a, f))

    // call overridden replace if present
    static member inline replace< ^a, ^b, ^c when ^b: (static member replace: ^a * ^b -> ^c)>(a: ^a, f: ^b) =
        (^b : (static member replace: ^a * ^b -> ^c) (a, f))

let inline replace_instance< ^a, ^b, ^c, ^d when (^a or ^c): (static member replace: ^b * ^c -> ^d)> (a: ^b, f: ^c) =
        ((^a or ^c): (static member replace: ^b * ^c -> ^d) (a, f))

// Note the concrete type 'CFunctor' specified in the signature
let inline replace (a: ^a) (f: ^b): ^a0 when (CFunctor or  ^b): (static member replace: ^a *  ^b ->  ^a0) =
    replace_instance<CFunctor, _, _, _> (a, f)

Vedi anche