Parámetros de tipo resueltos estáticamente
Un parámetro de tipo resuelto estáticamente es un parámetro de tipo que se reemplaza por un tipo real en tiempo de compilación en lugar de en tiempo de ejecución.
Syntax
'type-parameter
Hasta la versión 7.0 de F#, había que usar la siguiente sintaxis:
^type-parameter
Comentarios
En F# hay dos tipos distintos de parámetros de tipo. En primer lugar está el parámetro de tipo genérico estándar. Equivale a los parámetros de tipo genérico de otros lenguajes .NET. El segundo tipo de parámetro se resuelve estáticamente y solo se puede usar en funciones insertadas.
Los parámetros de tipo resueltos estáticamente son sobre todo útiles cuando se usan con restricciones de miembro, que son restricciones que permiten especificar que un argumento de tipo debe tener uno o varios miembros determinados. Este tipo de restricción no se puede crear mediante un parámetro de tipo genérico normal.
En la siguiente tabla, se resumen las similitudes y las diferencias entre las dos clases de parámetros de tipo.
Característica | Genérico | Se resuelve estáticamente |
---|---|---|
Tiempo de resolución | Tiempo de ejecución | Tiempo de compilación |
Restricciones de miembro | No se puede utilizar con restricciones de miembro. | Se puede utilizar con restricciones de miembro. |
Generación de código | Un tipo o método con parámetros de tipo genérico estándar da lugar a la generación de un solo tipo o método genérico. | Se generan varias instancias de los tipos y métodos, una por cada tipo que se necesita. |
Uso con tipos | Se puede utilizar con tipos. | No se puede utilizar con tipos. |
Uso con funciones inline | Una función inline no puede tener un parámetro de tipo genérico estándar. Si las entradas no son totalmente genéricas, el compilador de F# las especializa o, si no hay opciones para especializar, produce un error. | Los parámetros de tipo resueltos estáticamente no se pueden utilizar con funciones o métodos que no sean inline. |
Muchas funciones de la biblioteca básica de F#, sobre todo los operadores, tienen parámetros de tipo que se resuelven estáticamente. Se trata de funciones y operadores inline, que dan lugar a una generación de código eficaz para los cálculos numéricos.
Los métodos y funciones inline que usan operadores u otras funciones que utilicen parámetros de tipo resueltos estáticamente, también pueden emplear ellos mismos este tipo de parámetro. En muchas ocasiones, durante la inferencia de tipos, se deduce que esas funciones inline tienen parámetros de tipo que se resuelven estáticamente. En el siguiente ejemplo, se muestra una definición de operador para la cual se deduce que tiene un parámetro de tipo que se resuelve estáticamente.
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)
El tipo resuelto de (+@)
se basa en el uso de (+)
y (*)
, que hacen que mediante la inferencia de tipos se deduzcan las restricciones de miembro en los parámetros de tipo estáticamente resueltos. Tal y como se muestra en el intérprete de F#, el tipo resuelto es el siguiente.
'a -> 'c -> 'd
when ('a or 'b) : (static member ( + ) : 'a * 'b -> 'd) and
('a or 'c) : (static member ( * ) : 'a * 'c -> 'b)
La salida es la siguiente.
2
1.500000
En el ejemplo siguiente se muestra el uso de SRTP con métodos y 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 de F# 7.0, puede usar 'a.Zero()
en lugar de tener que repetir la restricción como en el ejemplo siguiente.
A partir de F# 4.1, también puede especificar nombres de tipo concretos en firmas de parámetros de tipo resueltos estáticamente. En versiones anteriores del lenguaje, el compilador deducía el nombre de tipo, que no se podía especificar en la firma. A partir de F# 4.1, también puede especificar nombres de tipo concretos en firmas de parámetros de tipo resueltos estáticamente. A continuación se incluye un ejemplo (tenga en cuenta que, en este ejemplo, se debe seguir usando ^
porque no se admite la simplificación para 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)