Generalización automática

F# usa inferencia de tipos para evaluar los tipos de funciones y expresiones. En este tema se explica cómo F# generaliza automáticamente los argumentos y los tipos de funciones para que interactúen con varios tipos cuando esto sea posible.

Generalización automática

El compilador de F#, cuando realiza la inferencia de tipos en una función, determina si un parámetro determinado puede ser genérico. El compilador examina cada parámetro y determina si la función tiene una dependencia en el tipo específico de ese parámetro. Si no es así, se deduce que el tipo es genérico.

En el ejemplo de código siguiente se muestra una función que el compilador deduce que es genérica.

let max a b = if a > b then a else b

El tipo se deduce como 'a -> 'a -> 'a.

El tipo indica que se trata de una función que toma dos argumentos del mismo tipo desconocido y devuelve un valor de ese mismo tipo. Uno de los motivos por los que la función anterior puede ser genérica es que el operador mayor que (>) es en sí mismo genérico. El operador mayor que tiene la firma 'a -> 'a -> bool. No todos los operadores son genéricos y, si el código de una función usa un tipo de parámetro junto con una función o un operador no genéricos, ese tipo de parámetro no se puede generalizar.

Dado que max es genérico, se puede usar con tipos como int, float, etc., como se muestra en los ejemplos siguientes.

let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3

Pero los dos argumentos deben ser del mismo tipo. La firma es 'a -> 'a -> 'a, no 'a -> 'b -> 'a. Por lo tanto, el código siguiente genera un error porque los tipos no coinciden.

// Error: type mismatch.
let biggestIntFloat = max 2.0 3

La función max también funciona con cualquier tipo que admita el operador mayor que. Por lo tanto, también puede usarla en una cadena, como se muestra en el código siguiente.

let testString = max "cab" "cat"

Restricción de valor

El compilador realiza la generalización automática solo en definiciones de función completas que tienen argumentos explícitos y en valores inmutables simples.

Esto significa que el compilador emite un error si se intenta compilar código que no está suficientemente restringido para ser de un tipo específico, pero que tampoco es generalizable. El mensaje de error de este problema se refiere a esta restricción en la generalización automática de valores como restricción de valor.

Normalmente, el error de restricción de valor se produce cuando se quiere que una construcción sea genérica pero el compilador no tiene información suficiente para generalizarla, o cuando se omite accidentalmente suficiente información de tipo de una construcción no genérica. La solución al error de restricción de valor es proporcionar información más explícita para restringir más completamente el problema de inferencia de tipos, de una de las maneras siguientes:

  • Restrinja un tipo para que no sea genérico mediante la incorporación de una anotación de tipo explícita a un valor o parámetro.

  • Si el problema usa una construcción no generalizable para definir una función genérica, como una composición de función o argumentos de función currificados aplicados de forma incompleta, intente volver a escribir la función como una definición de función normal.

  • Si el problema es una expresión demasiado compleja para generalizarse, conviértala en una función mediante la incorporación de un parámetro adicional sin usar.

  • Agregue parámetros de tipo genérico explícitos. Esta opción se usa raramente.

En los siguientes ejemplos de código se muestran estos escenarios.

Caso 1: Una expresión demasiado compleja. En este ejemplo, la lista counter está pensada para ser int option ref, pero no se ha definido como un valor inmutable simple.

let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None

Caso 2: Uso de una construcción no generalizable para definir una función genérica. En este ejemplo, la construcción no es generalizable, porque conlleva la aplicación parcial de argumentos de función.

let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj

Caso 3: Incorporación de un parámetro adicional sin usar. Dado que esta expresión no es lo suficientemente sencilla para la generalización, el compilador emite el error de restricción de valor.

let emptyList10 = Array.create 10 []
// Adding an extra (unused) parameter makes it a function, which is generalizable.
let emptyList10 () = Array.create 10 []

Caso 4: Incorporación de parámetros de tipo.

let arrayOf10Lists = Array.create 10 []
// Adding a type parameter and type annotation lets you write a generic value.
let arrayOf10Lists<'T> = Array.create 10 ([]:'T list)

En el último caso, el valor se convierte en una función de tipo que se puede usar para crear valores de muchos tipos diferentes, por ejemplo, los siguientes:

let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>

Consulte también