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#?
Unfortunately F# doesn;t support co\contravariance. 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
Use Seq.cast for this.  For example:
Seq.singleton "Hello World"  |> Seq.cast |> printEm
Admittedly, this gives up type safety:
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!
but it is expedient.
Alternatively, you can use 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
which is just shorthand for the generic "forall types 'a when 'a :> Dog".  
And finally, you can always map the upcast, e.g.
let printEm (os: seq<obj>) =  
    for o in os do 
        o.ToString() |> printfn "%s" 
Seq.singleton "Hello" |> Seq.map box |> printEm // ok
where box upcasts to obj.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With