Tipos flexíveis

Uma anotação de tipo flexível indica que um parâmetro, variável ou valor tem um tipo compatível com um tipo especificado, em que a compatibilidade é determinada pela posição em uma hierarquia de classes ou interfaces orientada a objetos. Os tipos flexíveis são úteis especificamente quando a conversão automática para tipos superiores na hierarquia de tipos não ocorre, mas você ainda deseja habilitar sua funcionalidade para trabalhar com qualquer tipo na hierarquia ou qualquer tipo que implemente uma interface.

Sintaxe

#type

Comentários

Na sintaxe anterior, tipo representa um tipo base ou uma interface.

Um tipo flexível é equivalente a um tipo genérico que possui uma restrição que limita os tipos permitidos a tipos compatíveis com o tipo base ou de interface. Ou seja, as duas linhas de código a seguir são equivalentes.

#SomeType

'T when 'T :> SomeType

Os tipos flexíveis são úteis em vários tipos de situações. Por exemplo, quando você tem uma função de ordem superior (uma função que recebe uma função como argumento), geralmente é útil que a função retorne um tipo flexível. No exemplo a seguir, o uso de um tipo flexível com um argumento de iterate2 sequência permite que a função de ordem superior funcione com funções que geram sequências, matrizes, listas e qualquer outro tipo enumerável.

Considere as duas funções a seguir, uma das quais retorna uma sequência, a outra retorna um tipo flexível.

let iterate1 (f : unit -> seq<int>) =
    for e in f() do printfn "%d" e
let iterate2 (f : unit -> #seq<int>) =
    for e in f() do printfn "%d" e

// Passing a function that takes a list requires a cast.
iterate1 (fun () -> [1] :> seq<int>)

// Passing a function that takes a list to the version that specifies a
// flexible type as the return value is OK as is.
iterate2 (fun () -> [1])

Como outro exemplo, considere a função de biblioteca Seq.concat:

val concat: sequences:seq<#seq<'T>> -> seq<'T>

Você pode passar qualquer uma das seguintes sequências enumeráveis para esta função:

  • Uma lista de listas
  • Uma lista de matrizes
  • Uma matriz de listas
  • Uma matriz de sequências
  • Qualquer outra combinação de sequências enumeráveis

O código a seguir usa Seq.concat para demonstrar os cenários que você pode dar suporte usando tipos flexíveis.

let list1 = [1;2;3]
let list2 = [4;5;6]
let list3 = [7;8;9]

let concat1 = Seq.concat [ list1; list2; list3]
printfn "%A" concat1

let array1 = [|1;2;3|]
let array2 = [|4;5;6|]
let array3 = [|7;8;9|]

let concat2 = Seq.concat [ array1; array2; array3 ]
printfn "%A" concat2

let concat3 = Seq.concat [| list1; list2; list3 |]
printfn "%A" concat3

let concat4 = Seq.concat [| array1; array2; array3 |]
printfn "%A" concat4

let seq1 = { 1 .. 3 }
let seq2 = { 4 .. 6 }
let seq3 = { 7 .. 9 }

let concat5 = Seq.concat [| seq1; seq2; seq3 |]

printfn "%A" concat5

A saída é a seguinte.

seq [1; 2; 3; 4; ...]
seq [1; 2; 3; 4; ...]
seq [1; 2; 3; 4; ...]
seq [1; 2; 3; 4; ...]
seq [1; 2; 3; 4; ...]

Em F#, como em outras linguagens orientadas a objetos, há contextos nos quais tipos derivados ou tipos que implementam interfaces são convertidos automaticamente em um tipo base ou tipo de interface. Essas conversões automáticas ocorrem em argumentos diretos, mas não quando o tipo está em uma posição subordinada, como parte de um tipo mais complexo, como um tipo de retorno de um tipo de função ou como um argumento de tipo. Assim, a notação de tipo flexível é útil principalmente quando o tipo ao qual você a está aplicando faz parte de um tipo mais complexo.

Confira também