F#y covarianza de interfaz: ¿qué hacer?(específicamente seq<> aka IEnumerable<>)
casting covariance (2)
Estoy intentando llamar a un método .NET que acepte un IEnumerable<T>
genérico de F # usando un seq<U>
tal que U es una subclase de T. Esto no funciona de la manera que esperaba:
Con la siguiente impresora simple:
let printEm (os: seq<obj>) =
for o in os do
o.ToString() |> printfn "%s"
Estos son los resultados que obtengo:
Seq.singleton "Hello World" |> printEm // error FS0001;
//Expected seq<string> -> ''a but given seq<string> -> unit
Seq.singleton "Hello World" :> seq<obj> |> printEm // error FS0193;
//seq<string> incompatible with seq<obj>
Seq.singleton "Hello World" :?> seq<obj> |> printEm // works!
Seq.singleton 42 :> seq<obj> |> printEm // error FS0193
Seq.singleton 42 :?> seq<obj> |> printEm // runtime InvalidCastException!
//Unable to cast object of type ''mkSeq@541[System.Int32]''
// to type ''System.Collections.Generic.IEnumerable`1[System.Object]''.
Idealmente, me gustaría que la primera sintaxis funcione, o algo lo más cercano posible, con la verificación del tipo de tiempo de compilación. No entiendo dónde encuentra el compilador una función seq<string> -> unit
en esa línea, pero aparentemente la covarianza para IEnumerable no funciona y eso de alguna manera da como resultado ese mensaje de error. El uso de una conversión explícita da como resultado un mensaje de error razonable, pero tampoco funciona. Usar un casting en tiempo de ejecución funciona, pero solo para cadenas, las entradas fallan con una excepción (desagradable).
Estoy tratando de interoperar con otro código .NET; es por eso que necesito tipos específicos de IEnumerable.
¿Cuál es la forma más limpia y preferiblemente eficiente de convertir interfaces co o contravariantes como IEnumerable en F #?
Lamentablemente, F # doesn; t admite co / contravariance. Es por eso que esto
Seq.singleton "Hello World" :> seq<obj> |> printEm
no funciona
Puede declarar el parámetro como seq <_>, o limitar el conjunto de tipos de parámetros a una familia específica mediante el uso de tipo flexible s (con hash #), esto solucionará este escenario:
let printEm (os: seq<_>) =
for o in os do
o.ToString() |> printfn "%s"
Seq.singleton "Hello World" |> printEm
Teniendo en cuenta estas líneas:
Seq.singleton 42 :> seq<obj> |> printEm // error FS0193
Seq.singleton 42 :?> seq<obj> |> printEm
La varianza solo funciona para las clases, por lo que un código similar tampoco funcionará en C #.
Puede intentar emitir elementos de secuencia a la explicidad de tipo requerida a través de Seq.cast
Usa Seq.cast
para esto. Por ejemplo:
Seq.singleton "Hello World" |> Seq.cast |> printEm
Es cierto que esto renuncia a la seguridad del tipo:
type Animal() = class end
type Dog() = inherit Animal()
type Beagle() = inherit Dog()
let printEm (os: seq<Dog>) =
for o in os do
o.ToString() |> printfn "%s"
Seq.singleton (Beagle()) |> Seq.cast |> printEm // ok
Seq.singleton (Animal()) |> Seq.cast |> printEm // kaboom!
pero es conveniente.
Alternativamente, puede usar tipos flexibles :
type Animal() = class end
type Dog() = inherit Animal()
type Beagle() = inherit Dog()
let printEm (os: seq<#Dog>) = // note #Dog
for o in os do
o.ToString() |> printfn "%s"
Seq.singleton (Beagle()) |> printEm // ok
Seq.singleton (Animal()) |> printEm // type error
que es solo una abreviatura de los "tipos de forall" genéricos ''a when ''a :> Dog
".
Y finalmente, siempre puedes mapear el upcast, por ejemplo
let printEm (os: seq<obj>) =
for o in os do
o.ToString() |> printfn "%s"
Seq.singleton "Hello" |> Seq.map box |> printEm // ok
donde la box
sube a obj
.