Parâmetros de tipo resolvidos estaticamente

Um parâmetro de tipo resolvido estaticamente é um parâmetro de tipo que é substituído por um tipo real em tempo de compilação em vez de em tempo de execução.

Sintaxe

'type-parameter

Até a versão 7.0 do F#, era preciso usar a sintaxe a seguir

^type-parameter

Comentários

Em F#, existem dois tipos distintos de parâmetros de tipo. O primeiro tipo é o parâmetro de tipo genérico padrão. Elas são equivalentes a parâmetros de tipo genérico em outras linguagens .NET. O outro tipo é resolvido estaticamente e só pode ser usado em funções embutidas.

Parâmetros de tipo resolvidos estaticamente são úteis principalmente em conjunto com restrições de membro, que são restrições que permitem especificar que um argumento de tipo deve ter um membro ou membros específicos para ser usado. Não é possível criar esse tipo de restrição usando um parâmetro de tipo genérico regular.

A tabela a seguir resume as semelhanças e diferenças entre os dois tipos de parâmetros de tipo.

Recurso Genérico Resolvidos estaticamente
Tempo de resolução Tempo de execução Tempo de compilação
Restrições de membro Não pode ser usado com restrições de membro. Pode ser usado com restrições de membro.
Geração de código Um tipo (ou método) com parâmetros de tipo genérico padrão resulta na geração de um único tipo ou método genérico. Várias instanciações de tipos e métodos são geradas, uma para cada tipo necessário.
Usar com tipos Pode ser usado em tipos. Não pode ser usado em tipos.
Usar com funções internas Uma função embutida não pode ser parametrizada com um parâmetro de tipo genérico padrão. Se as entradas não forem totalmente genéricas, o compilador F# as especializará ou, se não houver opções para especializar, apresentará um erro. Parâmetros de tipo resolvidos estaticamente não podem ser usados em funções ou métodos que não são embutidos.

Muitas funções da biblioteca principal do F#, especialmente operadores, têm parâmetros de tipo resolvidos estaticamente. Essas funções e operadores são embutidos e resultam em geração de código eficiente para cálculos numéricos.

Métodos e funções embutidos que usam operadores ou usam outras funções que têm parâmetros de tipo resolvidos estaticamente também podem usar os próprios parâmetros de tipo resolvidos estaticamente. Frequentemente, a inferência de tipos infere que essas funções embutidas têm parâmetros de tipo resolvidos estaticamente. O exemplo a seguir ilustra uma definição de operador que é inferida como tendo um parâmetro de tipo resolvido estaticamente.

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)

O tipo resolvido de (+@) é baseado no uso de (+) e (*), ambos fazendo com que a inferência de tipo infira restrições de membro nos parâmetros de tipo resolvidos estaticamente. O tipo resolvido, conforme mostrado no interpretador F#, é o seguinte.

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

A saída é a seguinte.

2
1.500000

O seguinte exemplo ilustra o uso de SRTPs com métodos e métodos estáticos:

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 partir do F# 7.0, você pode usar 'a.Zero() em vez de precisar repetir a restrição como no exemplo abaixo.

A partir do F# 4.1, você também pode especificar nomes de tipo concretos em assinaturas de parâmetro de tipo resolvidas estaticamente. Nas versões anteriores da linguagem, o nome do tipo era inferido pelo compilador, mas não podia ser especificado na assinatura. A partir do F# 4.1, você também pode especificar nomes de tipo concretos em assinaturas de parâmetro de tipo resolvidos estaticamente. Veja abaixo um exemplo (observe que, nesse exemplo, ^ ainda precisa ser usado porque não há suporte para a simplificação que permite usar '):

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)

Confira também