泛型约束中的 F# 错误

F# error in generic constraint

以下F#代码

let f<'T when 'T: (member Id:int)> (t:'T) = t.Id

未被接受,出现以下错误:

Error FS0670 This code is not sufficiently generic. The type variable ^T when ^T : (member get_Id : ^T -> int) could not be generalized because it would escape its scope.

怎么了? 如何解决?

编辑

@Fyodor:棘手!我做了一些测试,发现了更多的奇怪之处:

let inline f1<^T when ^T: (member Id:int)> (t:^T) = ( ^T: (member Id:int) t )

let inline f2<'T when 'T: (member Id:int)> (t:'T) = ( 'T: (member Id:int) t )

let inline f3<'T when 'T: (member Id:int)> (t:'T) = ( ^T: (member Id:int) t )

let inline f4 t = ( ^T: (member Id:int) t )

f1 在 <^T

中给出错误

Error FS0010 Unexpected infix operator in pattern

f2 在 ( 'T

中给出错误

Error FS0583 Unmatched '('

Error FS0010 Unexpected quote symbol in binding

f3 和 f4 被接受

你犯了三个错误:

首先,函数需要inline。 .NET CLR 当前不支持成员约束(即 "this can be any type as long as it has this member"),这意味着此类函数无法编译为 IL,因此 F# 编译器必须 伪造它 并替换这些函数在编译时。要向编译器发出您知道并同意的信号,您必须在 let 之后添加 inline 关键字。内联函数将在编译时被完全擦除,并且不会在编译代码中显示为 CLR 方法。

其次,泛型参数名需要前缀^而不是'。这实际上是可选的,因为编译器似乎会自动将 ' 替换为 ^ (从您的错误消息中可以明显看出),但这只是为了保持一致性。以 ^ 为前缀的泛型参数称为 "statically resolved type parameters",这是指它们在编译时被解析(和擦除)的事实,如上所述。

第三,在函数体中引用此类成员的语法实际上与引用常规成员的语法并不相同。您不能使用点符号。相反,您必须使用这种反映参数声明的奇怪语法。

应用所有三个修复程序,这将是您的新代码:

let inline f<^T when ^T: (member Id:int)> (t:^T) = ( ^T: (member Id:int) t )

注意,由于成员约束现在在函数体中,所以不必在=的左边重复,所以可以这样写:

let inline f t = ( ^T: (member Id:int) t )

.

要添加关于您添加的编辑的简短评论 - 您定义 f1 的问题很简单,解析器需要在尖括号和帽子之间添加 space:<^:

let inline f1< ^T when ^T: (member Id:int)> (t:^T) = ( ^T: (member Id:int) t )

否则,语法 <^ 将被解析为运算符,而不是通用参数列表,这正是您在这里需要的。所有其他信息都在 Fyodor 的回答中!