Tipos flexibles (F#)
Una anotación de tipo flexible indica que un parámetro, una variable o un valor tiene un tipo compatible con el tipo especificado, donde la compatibilidad viene determinada por la posición en una jerarquía orientada a objetos de clases o interfaces. Los tipos flexibles resultan útiles específicamente cuando no se produce la conversión automática a los tipos que se encuentran por encima en la jerarquía de tipos, pero se desea habilitar la funcionalidad para que funcione con cualquiera de los tipos de la jerarquía o con cualquier tipo que implemente una interfaz.
#type
Comentarios
En la sintaxis anterior, type representa un tipo base o una interfaz.
Un tipo flexible equivale a un tipo genérico que tiene una restricción que limita los tipos permitidos a los tipos que son compatibles con el tipo base o de interfaz. Es decir, las siguientes dos líneas de código son equivalentes.
#SomeType
'a when 'a :> SomeType
Los tipos flexibles son útiles en varios tipos de situaciones. Por ejemplo, cuando se tiene una función de orden superior (una función que toma una función como argumento), suele resultar útil 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 con funciones que generan secuencias, matrices, listas y cualquier otro tipo enumerable.
Fíjese en las dos funciones las siguientes: una de ellas devuelve una secuencia; la otra, 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])
La función de biblioteca Seq.concat representa otro ejemplo:
val concat: sequences:seq<#seq<'T>> -> seq<'T>
Puede pasar cualquiera de las secuencias enumerables siguientes 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 utiliza Seq.concat para mostrar los escenarios que se pueden admitir mediante los 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#, al igual que en otros lenguajes orientados a objetos, hay contextos en que los tipos derivados o los tipos que implementan interfaces se convierten automáticamente a un tipo base o tipo de interfaz. Estas conversiones automáticas se producen en argumentos directos, pero no cuando el tipo se encuentra 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 un argumento de tipo). Así pues, la notación de tipo flexible resulta útil principalmente cuando el tipo al que se aplica forma parte de un tipo más complejo.