F#和界面协方差:该怎么办?(特别是seq <> aka IEnumerable <>)

Eam*_*nne 12 ienumerable f# casting covariance

我试图调用一个.NET方法接受IEnumerable<T>来自F#的泛型,使用seq<U>U是T的子类.这不会像我预期的那样工作:

使用以下简单的打印机:

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

这些是我得到的结果:

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]'.
Run Code Online (Sandbox Code Playgroud)

理想情况下,我希望第一种语法可以工作 - 或者使用尽可能接近它的东西,使用编译时类型检查.我不明白编译器seq<string> -> unit在该行中找到函数的位置,但显然IEnumerable的协方差不起作用,并以某种方式导致该错误消息.使用显式强制转换会产生合理的错误消息 - 但它也不起作用.使用运行时强制转换可以工作 - 但仅限于字符串,int会因异常(令人讨厌)而失败.

我正在尝试与其他.NET代码进行互操作; 这就是为什么我需要特定的IEnumerable类型.

什么是最简洁,最有效的方法来构建共同或逆变接口,例如F#中的IEnumerable?

des*_*sco 10

不幸的是,F#并不支持共同逆转.这就是为什么这个

Seq.singleton "Hello World"  :> seq<obj> |> printEm 
Run Code Online (Sandbox Code Playgroud)

不起作用

您可以将参数声明为seq <_>,或者通过使用灵活类型 s(使用hash#)将参数类型集限制为某个特定系列,这将修复此方案:

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

Seq.singleton "Hello World"  |> printEm 
Run Code Online (Sandbox Code Playgroud)

考虑到这条线:

Seq.singleton 42 :> seq<obj> |> printEm // error FS0193
Seq.singleton 42 :?> seq<obj> |> printEm
Run Code Online (Sandbox Code Playgroud)

方差仅适用于类,因此类似的代码也不适用于C#.

您可以尝试通过Seq.cast将序列元素转换为所需的类型明确性


Bri*_*ian 8

使用Seq.cast此.例如:

Seq.singleton "Hello World"  |> Seq.cast |> printEm
Run Code Online (Sandbox Code Playgroud)

不可否认,这放弃了类型安全:

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!
Run Code Online (Sandbox Code Playgroud)

但这是权宜之计.

或者,您可以使用灵活类型:

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
Run Code Online (Sandbox Code Playgroud)

这只是通用"forall类型'a when 'a :> Dog"的简写.

最后,您可以始终映射上传,例如

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

Seq.singleton "Hello" |> Seq.map box |> printEm // ok
Run Code Online (Sandbox Code Playgroud)

在哪里box向上倾斜obj.

  • 这是缓慢的,而不是静态的类型错误...不是我真正希望的 - 但它的工作原理简洁! (4认同)