F#嵌套泛型类型与实现类型不兼容

sni*_*10m 4 generics f#

背景:

在F#程序中给出以下两个声明:

  • type A实现接口Wrapped<int>
  • type B实现接口Wrapped<A>

我们说类型A兼容Wrapped<int>并且类型B兼容Wrapped<A>- 兼容,据我所知,这意味着A可以传递给需要的函数Wrapped<int>.

问题:

根据我的编程经验,鉴于以上两个陈述,我希望以下内容也是如此:

  • 类型B应与类型兼容Wrapped<Wrapped<int>>

因为BA作为类型参数,此处Wrapped<int>应该去,并AWrapped<int>兼容.

不是这种情况.以下实施:

type Wrapper<'a> = abstract member Value : 'a

type A =
    | A of int

    interface Wrapper<int> with member this.Value = (let (A num) = this in num)

type B =
    | B of A

    interface Wrapper<A> with member this.Value = (let (B a) = this in a)


let f (x:Wrapper<Wrapper<int>>) =
    x.Value.Value

let testValue = f (B (A 1))
Run Code Online (Sandbox Code Playgroud)

有一个编译错误B (A 1)说明

该类型B与类型不兼容Wrapper<Wrapper<int>>

题:

由于我能够逻辑地进行兼容性跳转,我在实现这个时做错了吗?或者F#没有这种"嵌套兼容性"功能,如果是这种情况,是否有特殊原因没有它?


有一个解决方法:

type B =
    | B of A

    interface Wrapper<Wrapper<int>> with member this.Value = (let (B a) = this in a :> Wrapper<int>)
Run Code Online (Sandbox Code Playgroud)

这将删除编译错误,虽然感觉有点不对劲.我问自己"如果我写一个函数来处理Wrapper<A>类型怎么办?(如果我添加了更多的Wrapper<A>实现者)

Ast*_*sti 5

您要求的功能是协变类型.

协方差允许返回类型,它是一个子类型,而不是泛型类型参数精确定义的类型(不是这仅适用于接口,而不适用于具体类型).这允许你向下转换IEnumerable<string> :?> IEnumerable<object>string :?> object.

可以在其他.NET语言中声明.这是你在C#中的例子:

interface Wrapper<out T> { }
class A : Wrapper<int> { }
class B : Wrapper<A> { }          

var b = new B();
Action<Wrapper<Wrapper<int>>> unwrap = _ => { };
unwrap(b); //compiles
Run Code Online (Sandbox Code Playgroud)

F#不提供对声明协变类型的支持,也没有在没有显式声明的情况下强制类型.其原因主要是协方差导致类型推理降级.

灵活类型可以实现F#中的协方差.这是F#seq中定义为的类型的示例IEnumerable<out T>.

let s = [1..10] 
let r =  s |> Seq.map(fun _ -> s)

let print1 (v: seq<seq<int>>) = printfn "%A" v
let print2 (v: seq<#seq<_>>) = printfn "%A" v

print1 r //does not compile
print2 r //compiles
Run Code Online (Sandbox Code Playgroud)

如果通用参数标记为协变并使用灵活类型,则有可能使这项工作成功.您可以在C#中使用接口声明并在F#中引用程序集.

还有mausch/VariantInterfaces根据命名约定修改程序集以添加协变/逆变声明,因此如果你在单独的程序集中有你的类型声明,你可以在后期构建中运行它.