Genéricos (F#)
Los valores de función, las propiedades, los métodos y los tipos agregados de F#, como las clases, los registros y las uniones discriminadas, pueden ser genéricos. Las construcciones genéricas contienen al menos un parámetro de tipo que, por lo general, proporciona el usuario de la construcción genérica. Las funciones y los tipos genéricos permiten escribir código que funciona con una variedad de tipos sin repetir el código para cada tipo. Hacer código genérico puede ser sencillo en F#, porque a menudo se infiere implícitamente el código para que sea genérico por la inferencia de tipos del compilador y los mecanismos de generalización automáticos.
// Explicitly generic function.
let function-name<type-parameters> parameter-list =
function-body
// Explicitly generic method.
[ static ] member object-identifer.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
Comentarios
La declaración explícita de una función o tipo genérico es muy parecida a la de una función o tipo no genérico, excepto en la especificación (y uso) de los parámetros de tipos, en corchetes angulares después del nombre del tipo o función.
Las declaraciones son a menudo implícitamente genéricas. Si no especifica completamente el tipo de cada parámetro que se usa para componer una función o un tipo, el compilador intenta inferir el tipo de cada parámetro, valor y variable del código que se escribe. Para obtener más información, vea Inferencia de tipos (F#). Si el código de su tipo o función no limita de otro modo los tipos de los parámetros, la función o el tipo son implícitamente genéricos. Este proceso se denomina generalización automática. Existen algunas limitaciones en la generalización automática. Por ejemplo, si el compilador de F# no puede inferir los tipos de una construcción genérica, informa sobre un error de una restricción denominada restricción de valor. En este caso, puede tener que agregar algunas anotaciones de tipo. Para obtener más información sobre la generalización automática y la restricción de valor, y cómo cambiar el código para resolver el problema, vea Generalización automática (F#).
En la sintaxis previa, type-parameters es una lista separada por comas de parámetros que representan tipos desconocidos, cada uno de los cuales comienza por una comilla simple, opcionalmente con una cláusula de restricción que limita aun más los tipos que se pueden usar con ese tipo de parámetro. Para obtener información sobre la sintaxis de las cláusulas de restricción de varios tipos y otra información sobre restricciones, vea Restricciones (F#).
El type-definition de la sintaxis es la misma que la definición de tipos de un tipo no genérico. Incluye los parámetros del constructor para un tipo de clase, una cláusula as opcional, el símbolo igual, los campos de registros, la cláusula inherit, las elecciones de una unión discriminada, enlaces let y do, definiciones de miembros y todo lo que se permite en una definición de tipos no genéricos.
Los demás elementos de la sintaxis son los mismos que los de los tipos y funciones no genéricos. Por ejemplo, object-identifier es un identificador que representa el objeto contenedor.
Los campos, métodos y constructores no pueden ser más genéricos que el tipo envolvente. Asimismo, los valores de un módulo no pueden ser genéricos.
Construcciones genéricas implícitas
Cuando el compilador de F# infiere los tipos del código, trata automáticamente todas las funciones que pueden ser genéricas como tales. Si especifica un tipo explícitamente, como un tipo de parámetro, impide la generalización automática.
En el ejemplo de código siguiente, makeList es genérico aunque no se haya declarado como tal y sus parámetros tampoco.
let makeList a b =
[a; b]
La firma de la función se infiere como 'a -> 'a -> 'a list. Observe que a y b en este ejemplo se infieren para tener el mismo tipo. Esto se debe a que se incluyen en una lista y todos los elementos de una lista deben ser del mismo tipo.
También puede hacer una función genérica usando la sintaxis de comillas simples en una anotación de tipo para indicar que un tipo de parámetro es un parámetro de tipo genérico. En el ejemplo de código siguiente, function1 es genérico porque sus parámetros se han declarado así, como parámetros de tipo.
let function1 (x: 'a) (y: 'a) =
printfn "%A %A" x y
Construcciones explícitamente genéricas
Para convertir una función en genérica, se declaran explícitamente sus parámetros de tipo entre corchetes angulares (< >). Esto se ilustra en el código siguiente:
let function2<'T> x y =
printfn "%A, %A" x y
Uso de construcciones genéricas
Cuando se usan funciones o métodos genéricos, no siempre es necesario especificar los argumentos de tipo. El compilador utiliza la inferencia de tipos para inferir los argumentos de tipo adecuados. Si la ambigüedad se mantiene, puede proporcionar argumentos de tipo en corchetes angulares y separarlos con comas.
En el código siguiente, se muestra el uso de las funciones que se definen en las secciones anteriores.
// 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
Nota
Hay dos maneras de referirse a un tipo genérico por nombre. Por ejemplo, list<int> e int list son dos formas de referirse a un tipo genérico list que tiene un argumento de tipo único int. La segunda forma se usa convencionalmente solo con tipos de F# integrados, como list y option. Si hay varios argumentos de tipo, normalmente se usa la sintaxis Dictionary<int, string> pero también puede usar (int, string) Dictionary.
Caracteres comodín como argumentos de tipo
Para especificar que el compilador debe inferir un argumento de tipo, puede usar un subrayado o símbolo comodín (_), en lugar de un argumento de tipo con nombre. Esto se muestra en el código siguiente.
let printSequence (sequence1: Collections.seq<_>) =
Seq.iter (fun elem -> printf "%s " (elem.ToString())) sequence1
Restricciones en los tipos y funciones genéricos
En una definición de función o tipo genérico, solo de pueden usar las construcciones que se sabe que están disponibles en el parámetro de tipo genérico. Esto es necesario para habilitar la comprobación de las llamadas a métodos y funciones en tiempo de compilación. Si se declaran explícitamente los parámetros de tipo, se podrá aplicar una restricción explícita a un parámetro de tipo genérico para indicar al compilador que están disponibles determinados métodos y funciones. Sin embargo, si se permite que el compilador de F# deduzca los tipos de parámetro genérico, este determinará las restricciones apropiadas. Para obtener más información, vea Restricciones (F#).
Parámetros de tipo resueltos estáticamente
Hay dos clases de parámetros de tipo que se pueden usar en programas de F#. La primera son parámetros de tipo genérico de la naturaleza descrita en las secciones previas. Esta primera clase equivale a los parámetros de tipo genérico que se usan en lenguajes como Visual Basic y C#. Otra clase de parámetro de tipo es específica de F# y se denomina parámetro de tipo resuelto estáticamente. Para obtener información sobre estas construcciones, vea Parámetros de tipo resueltos estáticamente (F#).
Ejemplos
// 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
Vea también
Referencia
Parámetros de tipo resueltos estáticamente (F#)