Generalização automática
F# usa inferência de tipo para avaliar os tipos de funções e expressões. Este tópico descreve como o F# generaliza automaticamente os argumentos e os tipos de funções para que eles funcionem com vários tipos quando isso for possível.
Generalização automática
O compilador F#, quando executa inferência de tipo em uma função, determina se um determinado parâmetro pode ser genérico. O compilador examina cada parâmetro e determina se a função tem uma dependência do tipo específico desse parâmetro. Caso contrário, infere-se que o tipo é genérico.
O exemplo de código a seguir ilustra uma função que o compilador infere ser genérica.
let max a b = if a > b then a else b
Infere-se que o tipo seja 'a -> 'a -> 'a
.
O tipo indica que esta é uma função que usa dois argumentos do mesmo tipo desconhecido e retorna um valor desse mesmo tipo. Uma das razões pelas quais a função anterior pode ser genérica é que o operador>
maior do que () é ele próprio genérico. O operador maior do que tem a assinatura 'a -> 'a -> bool
. Nem todos os operadores são genéricos, e se o código em uma função usa um tipo de parâmetro junto com uma função ou operador não genérico, esse tipo de parâmetro não pode ser generalizado.
Por max
ser genérico, ele pode ser usado com tipos como int
, float
, e assim por diante, como mostrado nos exemplos a seguir.
let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3
No entanto, os dois argumentos devem ser do mesmo tipo. A assinatura é 'a -> 'a -> 'a
, não 'a -> 'b -> 'a
. Portanto, o código a seguir produz um erro porque os tipos não correspondem.
// Error: type mismatch.
let biggestIntFloat = max 2.0 3
A max
função também funciona com qualquer tipo que suporte o operador maior do que. Portanto, você também pode usá-lo em uma cadeia de caracteres, como mostrado no código a seguir.
let testString = max "cab" "cat"
Restrição de valor
O compilador executa a generalização automática apenas em definições de função completas que têm argumentos explícitos e em valores imutáveis simples.
Isso significa que o compilador emite um erro se você tentar compilar um código que não é suficientemente restrito para ser um tipo específico, mas também não é generalizável. A mensagem de erro para esse problema refere-se a essa restrição na generalização automática para valores como a restrição de valor.
Normalmente, o erro de restrição de valor ocorre quando você deseja que uma construção seja genérica, mas o compilador tem informações insuficientes para generalizá-la, ou quando você omite involuntariamente informações de tipo suficientes em uma construção não genérica. A solução para o erro de restrição de valor é fornecer informações mais explícitas para restringir mais completamente o problema de inferência de tipo, de uma das seguintes maneiras:
Restringir um tipo a não ser genérico adicionando uma anotação de tipo explícita a um valor ou parâmetro.
Se o problema estiver usando uma construção não generalizável para definir uma função genérica, como uma composição de função ou argumentos de função curried aplicados incompletamente, tente reescrever a função como uma definição de função comum.
Se o problema for uma expressão muito complexa para ser generalizada, transforme-a em uma função adicionando um parâmetro extra não utilizado.
Adicione parâmetros de tipo genéricos explícitos. Esta opção raramente é usada.
Os exemplos de código a seguir ilustram cada um desses cenários.
Caso 1: Uma expressão demasiado complexa. Neste exemplo, a lista counter
destina-se a ser int option ref
, mas não é definida como um valor imutável simples.
let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None
Caso 2: Usando uma construção não generalizável para definir uma função genérica. Neste exemplo, a construção não é generalizável porque envolve a aplicação parcial de argumentos de função.
let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj
Caso 3: Adicionar um parâmetro extra não utilizado. Como essa expressão não é simples o suficiente para generalização, o compilador emite o erro de restrição 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: Adicionando 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)
No último caso, o valor torna-se uma função de tipo, que pode ser usada para criar valores de muitos tipos diferentes, por exemplo, da seguinte forma:
let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>