Tipos flexíveis
Uma anotação de tipo flexível indica que um parâmetro, variável ou valor tem um tipo que é compatível com um tipo especificado, onde a compatibilidade é determinada pela posição em uma hierarquia orientada a objeto de classes ou interfaces. Os tipos flexíveis são úteis especificamente quando a conversão automática para tipos mais altos 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
Observações
Na sintaxe anterior, type representa um tipo base ou uma interface.
Um tipo flexível é equivalente a um tipo genérico que tem uma restrição que limita os tipos permitidos a tipos que são 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 toma uma função como um 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 sequência em iterate2
permite que a função de ordem superior trabalhe 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 essa 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 suportar 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 automaticamente convertidos 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 é principalmente útil quando o tipo ao qual você está aplicando-a faz parte de um tipo mais complexo.