Statycznie rozwiązywane parametry typu

Statycznie rozpoznany parametr typu to parametr typu, który jest zastępowany rzeczywistym typem w czasie kompilacji zamiast w czasie wykonywania.

Składnia

'type-parameter

Do wersji 7.0 języka F#należy użyć następującej składni

^type-parameter

Uwagi

W języku F#istnieją dwa różne rodzaje parametrów typu. Pierwszy rodzaj to standardowy parametr typu ogólnego. Są one równoważne ogólnym parametrom typu w innych językach platformy .NET. Inny rodzaj jest statycznie rozpoznawany i może być używany tylko w funkcjach wbudowanych.

Statycznie rozwiązywane parametry typu są przydatne przede wszystkim w połączeniu z ograniczeniami składowymi, które są ograniczeniami, które umożliwiają określenie, że argument typu musi mieć określony element członkowski lub składowe, aby można było go użyć. Nie ma możliwości utworzenia tego rodzaju ograniczenia przy użyciu zwykłego parametru typu ogólnego.

W poniższej tabeli podsumowano podobieństwa i różnice między dwoma rodzajami parametrów typu.

Funkcja Ogólna Statycznie rozwiązane
Czas rozwiązywania Czas procesu Czas kompilacji
Ograniczenia składowe Nie można używać z ograniczeniami składowymi. Może być używany z ograniczeniami składowymi.
Generowanie kodu Typ (lub metoda) ze standardowymi parametrami typu ogólnego powoduje generowanie pojedynczego typu ogólnego lub metody. Generowane są wiele wystąpień typów i metod, po jednym dla każdego wymaganego typu.
Używanie z typami Można używać w typach. Nie można używać w typach.
Używanie z funkcjami wbudowanymi Funkcji wbudowanej nie można sparametryzować przy użyciu standardowego parametru typu ogólnego. Jeśli dane wejściowe nie są w pełni ogólne, kompilator języka F# je specjalizuje lub, jeśli nie ma opcji specjalizowania, zwraca błąd. Statycznie rozpoznane parametry typu nie mogą być używane w funkcjach lub metodach, które nie są wbudowane.

Wiele podstawowych funkcji bibliotek języka F#, zwłaszcza operatorów, ma statycznie rozwiązane parametry typu. Te funkcje i operatory są wbudowane i umożliwiają wydajne generowanie kodu na potrzeby obliczeń liczbowych.

Wbudowane metody i funkcje korzystające z operatorów lub używają innych funkcji, które mają statycznie rozpoznawane parametry typu, mogą również używać statycznie rozpoznanych parametrów typu. Często wnioskowanie typu wywnioskuje takie funkcje wbudowane, aby statycznie rozpoznać parametry typu. Poniższy przykład ilustruje definicję operatora, która jest wnioskowana o statycznie rozpoznanym parametrze typu.

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)

Rozpoznany typ jest oparty na użyciu parametrów (+@) i (+)(*), które powodują wnioskowanie typu wywnioskowania ograniczeń składowych na statycznie rozwiązanych parametrach typu. Rozpoznany typ, jak pokazano w interpreterze języka F#, jest następujący.

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

Dane wyjściowe są następujące:

2
1.500000

Poniższy przykład ilustruje użycie SRTPs z metodami i metodami statycznymi:

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

Począwszy od języka F# 7.0, można użyć 'a.Zero() zamiast powtarzać ograniczenie, jak w poniższym przykładzie.

Począwszy od języka F# 4.1, można również określić konkretne nazwy typów w statycznie rozpoznanych sygnaturach parametrów typu. W poprzednich wersjach języka nazwa typu została wywnioskowana przez kompilator, ale nie można jej określić w podpisie. Od wersji F# 4.1 można również określić konkretne nazwy typów w statycznie rozpoznanych sygnaturach parametrów typu. Oto przykład (nie należy tego używać w tym przykładzie, ^ ponieważ uproszczenie do użycia ' nie jest obsługiwane):

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)

Zobacz też