简体   繁体   English

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

[英]F# nested generic types not compatible with implemented type

Background: 背景:

Given the following two declarations in an F# program: 在F#程序中给出以下两个声明:

  • type A implements the interface Wrapped<int> 类型A实现接口Wrapped<int>
  • type B implements the interface Wrapped<A> 类型B实现Wrapped<A>接口

We say that type A is compatible with Wrapped<int> and type B is compatible with Wrapped<A> - compatible, to my understanding, meaning that an A can be passed into a function requiring a Wrapped<int> . 我们说类型AWrapped<int>兼容,类型BWrapped<A>兼容 - 据我所知,这意味着A可以传递给需要Wrapped<int>的函数。

Problem: 问题:

From my experience with programming, I would expect the following to also be true, given the above two statements: 根据我的编程经验,鉴于以上两个陈述,我希望以下内容也是如此:

  • type B should be compatible with the type Wrapped<Wrapped<int>> 类型B应与Wrapped<Wrapped<int>>类型兼容

since B has A as the type parameter where Wrapped<int> should go, and A and Wrapped<int> are compatible. 因为BA作为Wrapped<int>应该去的类型参数, AWrapped<int>是兼容的。

This is not the case. 不是这种情况。 The following implementation: 以下实施:

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))

has a compile error on B (A 1) stating B (A 1)上有一个编译错误

The type B is not compatible with the type Wrapper<Wrapper<int>> 类型BWrapper<Wrapper<int>>类型不兼容

Question: 题:

Since I was able to logically make the compatibility jump, am I doing something wrong while implementing this? 由于我能够逻辑地进行兼容性跳转,我在实现这个时做错了吗? Or does F# not have this "nested compatibility" feature, and if that's the case, is there a particular reason for not having it? 或者F#没有这种“嵌套兼容性”功能,如果是这种情况,是否有特殊原因没有它?


There is a workaround to this: 有一个解决方法:

type B =
    | B of A

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

That will remove the compile error, though it feels a little bit wrong. 这将删除编译错误,虽然感觉有点不对劲。 I ask myself "What if I ever write a function to work on Wrapper<A> types? (if I ever add more Wrapper<A> implementers) 我问自己“如果我编写一个函数来处理Wrapper<A>类型怎么办?(如果我添加更多Wrapper<A>实现者)

The feature you're asking for is covariant types. 您要求的功能是协变类型。

Covariance permits a return type which is a subtype rather than that exactly defined by the generic type parameter (not that this is applicable only to interfaces, not concrete types). 协方差允许返回类型,它是一个子类型,而不是泛型类型参数精确定义的类型(不是这仅适用于接口,而不适用于具体类型)。 This allows you to downcast IEnumerable<string> :?> IEnumerable<object> as string :?> object . 这允许您将IEnumerable<string> :?> IEnumerable<object>向下转换为string :?> object

Declaration is possible in the other .NET languages. 可以在其他.NET语言中声明。 Here's your example in C#: 这是你在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# does not provide support for declaring covariant types, nor does it coerce types without explicit declaration. F#不提供对声明协变类型的支持,也没有在没有显式声明的情况下强制类型。 The reason for this is mostly that covariance leads to degraded type inferencing. 其原因主要是协方差导致类型推理降级。

Covariance in F# is possible with flexible types . 使用灵活类型可以实现F#中的协方差。 Here's an example in F# on the seq type which is defined as IEnumerable<out T> . 这是在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

There is a chance you could make this work if the generic parameters were marked covariant and used flexible types. 如果通用参数标记为协变并使用灵活类型,则有可能使这项工作成功。 You could have the interface declarations in C# and reference the assembly in F#. 您可以在C#中使用接口声明并在F#中引用程序集。

There is also mausch/VariantInterfaces which modifies the assembly based on a naming convention to add covariant / contravariant declarations, so if you had your type declarations in a separate assembly, you could run it in post-build. 还有mausch / VariantInterfaces根据命名约定修改程序集以添加协变/逆变声明,因此如果你在单独的程序集中有你的类型声明,你可以在后期构建中运行它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM