2010-11-17 16 views
11

Sto provando a chiamare un metodo .NET che accetta un generico IEnumerable<T> da F # utilizzando un seq<U> tale che U sia una sottoclasse di T. Questo non funziona come mi aspettavo sarebbe:F # e covarianza di interfaccia: cosa fare? (in particolare seq <> alias IEnumerable <>)

Con la seguente semplice stampante:

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

Questi sono i risultati che ottengo:

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, mi piacerebbe la prima sintassi per lavorare - o qualcosa di più vicino al è come po ssible, con controllo del tipo di tempo compilato. Non capisco dove il compilatore trovi una funzione seq<string> -> unit in quella linea, ma apparentemente covarianza per IEnumerable non funziona e che in qualche modo si traduce in quel messaggio di errore. L'utilizzo di un cast esplicito genera un messaggio di errore ragionevole, ma non funziona neanche. L'uso di un cast runtime funziona, ma solo per le stringhe, gli ints falliscono con un'eccezione (sgradevole).

Sto cercando di interoperare con altri codici .NET; è per questo che ho bisogno di tipi specifici IEnumerable.

Qual è il modo più pulito e preferibilmente efficiente per il casting di interfacce co o controvarianti come IEnumerable in F #?

+1

Come dice desco, la soluzione più pulita è quella di modificare (o rimuovere) la dichiarazione del tipo su 'os' (se possibile). Su una nota non correlata, 'o.ToString |> printfn"% s "' può essere scritto in modo più conciso come 'o |> printfn"% O "'. – kvb

+0

@kvb Penso che @Eamon non abbia problemi con la funzione 'printfn'. –

risposta

8

Utilizzare Seq.cast per questo. Ad esempio:

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

Evidentemente, questo dà su tipo sicurezza:

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! 

ma è opportuno.

In alternativa, è possibile utilizzare 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 

che è solo una scorciatoia per il generico "forall tipi 'a when 'a :> Dog".

Infine, è possibile mappare sempre l'upcast, ad es.

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

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

dove box upcasts a obj.

+4

È lento, non staticamente registrato ... non proprio quello che speravo - ma funziona ed è conciso! –

9

Sfortunatamente F # non supporta la contravarianza. Ecco perché questo

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

non funziona

È possibile dichiarare il parametro come SEQ < _>, o limitare insieme di tipi di parametri per qualche famiglia specifico utilizzando flexible type s (con cancelletto #) questo risolverà questo scenario:

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

Seq.singleton "Hello World" |> printEm 

Considerando queste righe:

Seq.singleton 42 :> seq<obj> |> printEm // error FS0193 
Seq.singleton 42 :?> seq<obj> |> printEm 

La varianza funziona solo per le classi, quindi il codice simile non funziona anche in C#.

Si può provare a lanciare elementi di sequenza di tipo esplicitamente richiesto tramite Seq.cast

+0

Tuttavia, in C# l'equivalente di "Seq.singleton" Hello World "|> printEm' funzionerà e sarà controllato staticamente - che è il caso più interessante, per me. –

Problemi correlati