泛型的 F# 类型推断方法是什么?

What's the F# type inference approach to generics?

我正在尝试理解有关类型推断的规则,因为我想将其合并到我自己的语言中,本着这种精神,我一直在研究 F# 的类型推断,以下内容让我觉得很奇怪.

这会编译,而 id'a -> 'a,这(如果我没记错的话)意味着每次调用都使用 "fresh" 类型。

let id x = x

let id1 = id 1
let id2 = id "two"

但是当使用运算符时,似乎第一次调用决定了该函数的签名。

这里,mul被报告为int -> int -> int

let mul x y = x * y

let mul1 = mul 1 2
let mul2 = mul 1.1 2.2 // fails here

如果我重新排序,那么 mul 就是 float -> float -> float:

let mul x y = x * y

let mul2 = mul 1.1 2.2
let mul1 = mul 1 2 // fails here

您能否从类型检查实现的角度解释(最好是非学术的)规则是什么以及它是如何工作的?它是否会在每次引用函数时遍历函数以检查它们的类型?或者有其他方法吗?

F# 类型推断的这一方面在学术上并不是特别优雅,但在实践中效果很好。 F# 类型推断的工作方式是编译器最初将所有内容都视为类型变量(泛型类型)并收集对这些变量的约束。然后它会尝试解决这些约束。

例如,如果您有:

let callWithTen f = f 10   

然后,编译器最初分配类型,使得 callWithTen 的类型为 'af 的类型为 'b。它还收集以下约束:

  • 'a = 'a0 -> 'a1 因为 callWithTen 在句法上被定义为一个函数
  • 'a0 = 'b因为变量f是函数
  • 的参数
  • 'b = 'b0 -> 'b1因为变量f作为函数
  • 'b0 = int 因为 f 的参数是一个 int.
  • 'b1 = 'a1因为调用f的结果是callWithTen.
  • 的结果

解决这些约束,编译器然后推断 callWithTen 具有类型 (int -> 'b1) -> 'b1.

当你的代码中有 + 时,你不能完全确定数字类型到底是什么。其他一些 ML 语言通过对整数使用 + 并为 floating-point 数字使用 +. 来解决此问题,但这非常难看,因此 F# 采用不同的方法,有点 ad-hoc .

据我所知,F# 有一个类似于 'a supports (+) 的约束。所以,在你的情况下发生的事情(在稍微简化的描述中)是 add 是一个函数 'a0 -> 'a0 -> 'a0 其中 'a0 supports (+).

在处理其余代码时,编译器还会收集约束条件 'a0 = int(在第一次调用时)和 'a0 = float(在第二次调用时)。它首先解决了第一个问题,这很好(因为 int 支持 +)但随后它在第二个约束上失败了,因为 int != float 并在那里报告错误。

首先请注意,如果我们将 mul 声明为内联函数,则不会发生这种情况:

let inline mul x y = x * y

let mul1 = mul 1 2  // works
let mul2 = mul 1.1 2.2 // also works

此处mul的推断类型如下:

x: ^a -> y: ^b ->  ^c
    when ( ^a or  ^b) : (static member ( * ) :  ^a *  ^b ->  ^c)

这种类型意味着参数 xy 可以有任何类型(甚至不必是同一类型)只要它们中至少有一个有一个静态成员名为 * 的参数类型与 xy 相同。 mul 的 return 类型将与 * 成员的类型相同。

那么,当 mul 不是内联时,为什么不得到相同的行为呢?因为成员约束(即表示类型必须具有特定成员的类型约束)仅允许在内联函数上使用 - 这也是类型变量前面有 ^ 而不是通常的 ' 的原因:表示我们正在处理一种不同的、限制较少的类型变量。

那么为什么存在对 non-inline 函数的限制?由于 .NET 支持的内容。像 "T implements the interface I" 这样的类型约束可以在 .NET 字节码中表达,因此在所有函数中都是允许的。像 "T must have a specific member named X with type U" 这样的类型约束是不可表达的,因此在普通函数上是不允许的。由于内联函数在生成的 .NET 字节码中没有相应的方法,因此它们的类型不需要在 .NET 字节码中表达,因此限制不适用于它们。