Generalização automática

O F# usa inferência de tipos para avaliar os tipos de funções e expressões. Este tópico descreve como o F# generaliza automaticamente os argumentos e tipos de funções para que eles trabalhem com vários tipos quando isso for possível.

Generalização automática

O compilador F#, quando executa inferência de tipos em uma função, determina se um dado 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. Se ele não fizer isso, será inferido que o tipo é genérico.

O exemplo de código a seguir ilustra uma função que o compilador infere que é genérica.

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

É inferido que o tipo é 'a -> 'a -> 'a.

O tipo indica que essa é uma função que usa dois argumentos do mesmo tipo desconhecido e retorna um valor desse mesmo tipo. Um dos motivos pelos quais a função anterior pode ser genérica é que o operador maior que (>) é genérico. O operador maior que tem a assinatura 'a -> 'a -> bool. Nem todos os operadores são genéricos e, se o código em uma função usar um tipo de parâmetro junto com uma função ou operador não genérico, esse tipo de parâmetro não poderá ser generalizado.

Como max é genérico, ele pode ser usado com tipos como int, float etc., conforme 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 função max também funciona com qualquer tipo que dê suporte ao operador maior que. Portanto, você também pode usá-lo em uma cadeia de caracteres, conforme mostrado no código a seguir.

let testString = max "cab" "cat"

Restrição de valor

O compilador executa a generalização automática somente em definições de função completas que têm argumentos explícitos e em valores simples imutáveis.

Isso significa que o compilador emitirá um erro se você tentar compilar um código que não é suficientemente restrito para ser um tipo específico, mas que também não é generalizável. A mensagem de erro para esse problema se refere a essa restrição de 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 um constructo seja genérico, mas o compilador não tem informações suficientes para generalizá-lo ou quando você omite informações de tipo suficientes por engano em um constructo não genérico. A solução para o erro de restrição de valor é fornecer informações mais explícitas para restringir mais plenamente o problema de inferência de tipos de uma das seguintes maneiras:

  • Restrinja um tipo a ser não genérico adicionando uma anotação de tipo explícito a um valor ou parâmetro.

  • Se o problema estiver usando um constructo não generalizável para definir uma função genérica, como uma composição de função ou argumentos de função via currying incompletamente aplicados, tente reescrever a função como uma definição de função comum.

  • Se o problema for uma expressão complexa demais para ser generalizada, transforme-a em uma função adicionando um parâmetro extra não utilizado.

  • Adicione parâmetros explícitos de tipo genérico. Essa opção raramente é usada.

Os exemplos de código a seguir ilustram cada um desses cenários.

Caso 1: uma expressão complexa demais. Neste exemplo, a lista counter deve ser int option ref, mas não é definida como um valor simples imutável.

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

Caso 2: usar uma construção não generalizável para definir uma função genérica. Neste exemplo, o constructo 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: adicionar 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 se torna uma função de tipo, que pode ser usada para criar valores de muitos tipos diferentes, por exemplo, da seguinte maneira:

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

Confira também