2010-11-17 14 views
11

Estoy tratando de llamar a un método .NET aceptando un genérico IEnumerable<T> desde F # usando un seq<U> tal que U es una subclase de T. Esto no funciona de la manera que lo esperaba haría:F # y covarianza de interfaz: ¿qué hacer? (específicamente seq <> aka IEnumerable <>)

con la impresora siguiente sencillo:

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 '[email protected][System.Int32]' 
// to type 'System.Collections.Generic.IEnumerable`1[System.Object]'. 

Idealmente, me gusta la primera sintaxis para trabajar - o algo tan cerca como po posible, con comprobació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 de 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 transmitir interfaces co o contravariantes como IEnumerable en F #?

+1

Como dice desco, la solución más limpia es alterar (o eliminar) la declaración de tipo de 'os' (si es posible). En una nota no relacionada, 'o.ToString |> printfn"% s "' se puede escribir de forma más concisa como 'o |> printfn "% O" '. – kvb

+0

@kvb Creo que @Eamon no tiene problemas con la función 'printfn'. –

Respuesta

8

Use Seq.cast para esto. Por ejemplo:

Seq.singleton "Hello World" |> Seq.cast |> printEm 

Es cierto que esta renuncia a la seguridad de tipos:

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.

Como alternativa, puede utilizar flexible types:

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 está a la taquigrafía para el genérico "forall tipos 'a when 'a :> Dog".

Y, por último, siempre puede asignar el upcast, p. Ej.

let printEm (os: seq<obj>) = 
    for o in os do 
     o.ToString() |> printfn "%s" 

Seq.singleton "Hello" |> Seq.map box |> printEm // ok 

donde box upcasts a obj.

+4

Es lento, no está testeado estáticamente ... realmente no es lo que esperaba, ¡pero funciona y es conciso! –

9

Desafortunadamente F # doesn; t admite co \ contravariance. Es por eso que este

Seq.singleton "Hello World" :> seq<obj> |> printEm 

no funciona

Se puede declarar parámetro como SEC < _>, o el límite conjunto de tipos de parámetros a una familia específica mediante flexible type s (con almohadilla #) esta corregirá 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#.

Usted puede tratar de fundición elementos de secuencia explícita al tipo requerido a través de Seq.cast

+0

Sin embargo, en C# el equivalente de 'Seq.singleton 'Hello World' '> printEm' funcionará y se comprobará estáticamente, que es el caso más interesante para mí. –

Cuestiones relacionadas