Tipos flexibles
Una anotación de tipo flexible indica que un parámetro, una variable o un valor tiene un tipo que es compatible con un tipo especificado, de tal forma que la compatibilidad viene determinada por la posición en una jerarquía orientada a objetos de clases o interfaces. Los tipos flexibles son útiles específicamente cuando no se produce la conversión automática a tipos superiores en la jerarquía de tipos, pero aun así quiere permitir que la funcionalidad funcione con cualquier tipo de la jerarquía o cualquier tipo que implemente una interfaz.
Sintaxis
#type
Comentarios
En la sintaxis anterior, type representa un tipo base o una interfaz.
Un tipo flexible es equivalente a un tipo genérico que tiene una restricción que limita los tipos permitidos a tipos compatibles con el tipo base o de interfaz. Es decir, las dos líneas de código siguientes son equivalentes.
#SomeType
'T when 'T :> SomeType
Los tipos flexibles son útiles en varias situaciones. Por ejemplo, cuando tiene una función de orden superior (una función que toma una función como argumento), a menudo resulta útil hacer que la función devuelva un tipo flexible. En el ejemplo siguiente, el uso de un tipo flexible con un argumento de secuencia en iterate2
permite que la función de orden superior funcione correctamente con funciones que generan secuencias, matrices, listas y cualquier otro tipo enumerable.
Tenga en cuenta las dos funciones siguientes, una de las cuales devuelve una secuencia, mientras que la otra devuelve un tipo flexible.
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 otro ejemplo, considere la función de biblioteca Seq.concat:
val concat: sequences:seq<#seq<'T>> -> seq<'T>
Puede pasar cualquiera de las siguientes secuencias enumerables a esta función:
- Una lista de listas
- Una lista de matrices
- Una matriz de listas
- Una matriz de secuencias
- Cualquier otra combinación de secuencias enumerables
En el código siguiente se usa Seq.concat
para mostrar los escenarios que se pueden admitir mediante tipos flexibles.
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
La salida es la siguiente.
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; ...]
En F#, como en otros lenguajes orientados a objetos, hay contextos en los que los tipos o los tipos derivados que implementan interfaces se convierten automáticamente en un tipo base o un tipo de interfaz. Estas conversiones automáticas se producen en argumentos directos, pero no cuando el tipo está en una posición subordinada, como parte de un tipo más complejo (por ejemplo, un tipo de valor devuelto de un tipo de función) o como argumento de tipo. Por lo tanto, la notación de tipo flexible es útil principalmente cuando el tipo al que se aplica forma parte de un tipo más complejo.