F# 神秘的通用约束错误消息

F# cryptic generic constraint error message

只是为了好玩,我一直在使用 F# 中的模拟类型classes,使用所示的想法 here。我创建了一个 Next typeclass 来表示具有 "successor" 的值,例如下一个 1 = 2,下一个今天 = 明天等等:

type Next = Next with
    static member (++) (Next, x:int) = x + 1
    static member (++) (Next, x:DateTime) = x.AddDays 1.0
let inline next x = Next ++ x 

let v1 = next 1 // v1 = 2
let v2 = next DateTime.Now // v2 = Now + 1 day

现在我想在泛型中使用 "nextable" 类型 class:

type UsesNextable<'T>(nextable: 'T) = // Compile error 1
    member inline this.Next = 
        let v = next nextable         // Compile error 2
        v.ToString()

但我收到以下错误:

  1. 签名和实现不兼容,因为 class/signature 中的类型参数与 member/implementation
  2. 中的类型参数有不同的编译时要求
  3. 签名和实现不兼容,因为类型参数 'T' 的声明需要约束形式(Next 或 ^T): (static member (++) : Next * ^T - > ^?155882)

我对第二个错误试图表达的内容更感兴趣。神秘的 ^?155882 是什么意思?我猜这可能与无法解析类型有关。此外,假设它试图将约束推断为:

(Next or  ^T) : (static member ( ++ ) : Next *  ^T ->  ^T)

甚至可以使用 when 'T:... 指定 "or" 条件吗?我想不出有效的语法。

最后,^T 建议 statically resolved type parameter,但文档说它们不能用于类型。尽管如此,我尝试将 'T 更改为 ^T,但仍然出现相同的错误。

我意识到这个例子滥用了类型系统,所以我并不是说编译器应该能够处理它。但是,为了我自己的兴趣,我想知道错误消息的真正含义!

问题是 .NET 不支持静态约束。 F# 将它们解析为编译为静态方法的内联函数,但不能将其编码为标准的 .NET 类型。在您的代码中,您要创建的泛型类型将具有一个带有 .NET 无法表示的静态约束的类型参数。

您可以将您的方法更改为静态方法,它会正常工作:

type UsesNextable() =
    static member inline Next(nextable) = 
        let v = next nextable
        v.ToString()

如果你愿意,你的类型仍然可以是通用的,但你应该避免引用具有静态约束的类型的类型参数,例如这会起作用:

type UsesNextable<'T>() =
  static member inline NextOf(n:'U) = let v = next n in v.ToString()

但不是这个:

type UsesNextable<'T>() =
  static member inline NextOf(n:'T) = let v = next n in v.ToString()

解决了第一个问题后,您会看到两个错误消息都消失了,因为第二个错误消息与第一个问题相关,类型系统无法对约束进行编码,但现在它可以单独解决。 ^?155882 表示类型系统无法推断的静态约束类型变量。

请注意,从 'T 更改为 ˆT 不会改变任何内容,实际上两者都引用相同类型的变量,唯一的问题是您被迫使用 hat 当你调用方法时。

最后关于不能写的事实:

let inline next v = ((Next or  ^T) : (static member ( ++ ) : Next *  ^T ->  ^T) Next, v)

这显然是一个错误,因为 F# 可以推断它但您不能编写它,这是不一致的。我前段时间已经反映了,他们告诉我他们会在未来修复它。

有很多方法可以通过强制 F# 推断类型而不是直接编写类型来解决该限制,这是一个单行解决方案:

let inline next v = ((^Next or  ^T) : (static member ( ++ ) : ^Next *  ^T ->  ^T) Next, v)

它将创建一个警告,但您可以通过 nowarn 指令或向包含 Next.

类型实例的函数添加一个参数来禁用它