Genéricos
Los valores de función, los métodos, las propiedades 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. Convertir el código en 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.
Sintaxis
// Explicitly generic function.
let function-name<type-parameters> parameter-list =
function-body
// Explicitly generic method.
[ static ] member object-identifier.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 de la función o el tipo.
Las declaraciones a menudo son implícitamente genéricas. Si no se 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 más información, vea Inferencia de tipos. Si el código del 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 que hace referencia a una restricción denominada restricción de valor. En ese caso, puede que sea necesario agregar algunas anotaciones de tipo. Para 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.
En la sintaxis anterior, parámetros de tipo 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 aún más los tipos que se pueden usar para 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.
La definición de tipos 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 registro, la cláusula inherit
, las opciones para 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, identificador de objeto es un identificador que representa el objeto contenedor.
Las propiedades, campos y constructores no pueden ser más genéricos que el tipo envolvente. Además, 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 se especifica un tipo explícitamente, como un tipo de parámetro, se impide la generalización automática.
En el ejemplo de código siguiente, makeList
es genérico aunque no se ha 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 en este ejemplo a
y b
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 se puede hacer genérica una función 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 código siguiente, function1
es genérico porque sus parámetros se han declarado de esta manera, 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 (<type-parameter>
). Esto se ilustra en el código siguiente:
let function2<'T> (x: 'T) (y: 'T) =
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 usa la inferencia de tipos para inferir los argumentos de tipo adecuados. Si la ambigüedad se mantiene, se pueden 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 hacer referencia a un tipo genérico por nombre. Por ejemplo, list<int>
e int list
son dos formas de hacer referencia a un tipo genérico list
que tiene un único argumento de tipo int
. Por convención, la segunda forma solo se usa 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 se puede usar la sintaxis (int, string) Dictionary
.
Caracteres comodín como argumentos de tipo
Para especificar que el compilador debe inferir un argumento de tipo, se 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 se 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. Pero si se permite que el compilador de F# infiera los tipos de parámetro genéricos, determinará las restricciones apropiadas. Para más información, vea Restricciones.
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 los parámetros de tipo genérico de la naturaleza descrita en las secciones anteriores. 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.
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