背景
在F#程序中给出以下两个声明:
A
实现了接口Wrapped<int>
B
实现了接口Wrapped<A>
我们说类型A
与Wrapped<int>
兼容,类型B
与Wrapped<A>
兼容 - 兼容,据我所知,这意味着A
可以传递给需要Wrapped<int>
的函数。
问题:
根据我的编程经验,鉴于以上两个陈述,我希望以下内容也是如此:
B
应与Wrapped<Wrapped<int>>
因为B
A
作为Wrapped<int>
应该去的类型参数,A
和Wrapped<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))
在B (A 1)
陈述
类型不兼容
B
类型与Wrapper<Wrapper<int>>
问题:
由于我能够逻辑地进行兼容性跳转,我在实现这个时做错了吗?或者F#没有这个&#34;嵌套兼容性&#34;功能,如果是这样,是否有特殊原因没有它?
有一种解决方法:
type B =
| B of A
interface Wrapper<Wrapper<int>> with member this.Value = (let (B a) = this in a :> Wrapper<int>)
这会消除编译错误,虽然感觉有点不对劲。我问自己&#34;如果我写一个函数来处理Wrapper<A>
类型怎么办? (如果我添加了更多Wrapper<A>
实施者)
答案 0 :(得分: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
F#不提供对声明协变类型的支持,也没有在没有显式声明的情况下强制转换类型。其原因主要是协方差导致类型推理降级。
flexible types可以使用F#中的协方差。
以下是seq
类型的F#中的示例,其定义为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
如果通用参数标记为协变且使用灵活类型,则有可能使这项工作成功。您可以在C#中使用接口声明并在F#中引用程序集。
还有mausch/VariantInterfaces根据命名约定修改程序集以添加协变/逆变声明,因此如果您在单独的程序集中使用了类型声明,则可以在构建后运行它。