Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
765 views
in Technique[技术] by (71.8m points)

casting - F# and interface covariance: what to do? (specifically seq<> aka IEnumerable<>)

I'm trying to call a .NET method accepting a generic IEnumerable<T> from F# using a seq<U> such that U is a subclass of T. This doesn't work the way I expected it would:

With the following simple printer:

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

These are the results I get:

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]'.

Ideally, I'd like the first syntax to work - or something as close to it as possible, with compile time type checking. I don't understand where the compiler's finding a seq<string> -> unit function in that line, but apparently covariance for IEnumerable isn't working and that somehow results in that error message. Using an explicit cast results in a reasonable error message - but it doesn't work either. Using a runtime cast works - but only for strings, ints fail with an exception (nasty).

I'm trying to interoperate with other .NET code; that's why I need specific IEnumerable types.

What's the cleanest and preferably efficient way of casting co- or contravariant interfaces such as IEnumerable in F#?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Unfortunately F# doesn;t support cocontravariance. That's why this

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

doesn't work

You can declare parameter as seq<_>, or limit set of parameter types to some specific family by using flexible types (with hash #) this will fix this scenario:

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

Seq.singleton "Hello World"  |> printEm 

Considering this lines:

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

Variance only works for classes, so similar code will not work in C# too.

You can try casting sequence elements to required type explicity via Seq.cast


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...