f# casting ienumerable covariance

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 .