我在 SML 中有这个表达式,需要找到它最通用的类型。通过编译器运行时,我得到下面显示的内容。我将如何找到最通用的类型,不仅是这个函数,还有其他函数,比如教堂数字函数“二”。
val one = fn f => (fn x => f x)
为什么是这个类型:
('a -> 'b) -> 'a -> 'b
我在 SML 中有这个表达式,需要找到它最通用的类型。通过编译器运行时,我得到下面显示的内容。我将如何找到最通用的类型,不仅是这个函数,还有其他函数,比如教堂数字函数“二”。
val one = fn f => (fn x => f x)
为什么是这个类型:
('a -> 'b) -> 'a -> 'b
您所做的是,您应用一个称为Hindley-Milner类型推断的过程。
一般原则包括三个步骤:
首先,我们将未确定的类型(书面'Z、、、'Y等'X)分配给变量和表达式。
threehas 已经与 type 绑定int,那么在 中val nine = three * three,我们知道该threehas type int。first已经与 type 绑定'a * 'b -> 'a,那么在 中val firstOfFirst = fn quartet => first (first quartet),我们分配一个firsttype 'Z * 'Y -> 'Z,另一个分配 type 'X * 'W -> 'W。fnor rec),则该变量的所有出现都必须具有完全相同的类型——此时不允许多态。例如,在fn f => (f 1, f "one")(最终给出类型错误)中,我们最初分配所有出现f的单一类型'Z。(导致类型错误是因为我们稍后需要将其细化为int -> 'Yand string -> 'Y,而这些是矛盾的。这是因为标准 ML 不支持一流的多态性。)在您的示例中val one = fn f => (fn x => f x),我们可以分配ftype'Z和xtype 'Y。
接下来,我们执行类型统一,我们在其中识别必须匹配的不同子表达式的类型的不同部分。例如,如果我们知道 that fhas type'Z -> real并且 that ihas type int,那么如果我们看到f i,我们可以“统一” the'Z和 theint并得出结论 that fhas type int -> real。
在您的示例中,由于f应用于,我们可以与x统一,并最终分配类型。所以整个表达式都有 type 。'Z'Y -> ...f'Y -> 'X('Y -> 'X) -> 'Y -> 'X
最后,我们执行类型泛化。一旦我们执行了所有可以执行的统一——一旦我们推导出了关于类型的一切——我们可以安全地用绑定类型变量替换未确定的类型。在您的情况下,这让我们分配one类型方案 ∀ αβ 。(α → β) → α → β(意思是“对于任何和所有类型 α 和 β,one都有类型 (α → β) → α → β”)。在标准 ML 表示法中,我们不包括显式的“∀ αβ”部分;我们只是写('a -> 'b) -> 'a -> 'b。
我真的不明白你的问题,所以我只是猜测......
如果我在 REPL 中定义第一个教堂数字函数:
val c0 = fn f => fn x => x
val c1 = fn f => fn x => f x
val c2 = fn f => fn x => f (f x)
val c3 = fn f => fn x => f (f (f x))
val c4 = fn f => fn x => f (f (f (f x)))
...然后看看他们的类型:
val c0 = fn : 'a -> 'b -> 'b
val c1 = fn : ('a -> 'b) -> 'a -> 'b
val c2 = fn : ('a -> 'a) -> 'a -> 'a
val c3 = fn : ('a -> 'a) -> 'a -> 'a
val c4 = fn : ('a -> 'a) -> 'a -> 'a
...在第二个函数之后,类型('a ->'a) -> 'a -> 'a出现。这是您正在寻找的一般类型吗?
前两个函数的类型不同只是因为类型推断算法选择了最通用的类型。对于第一个函数'a -> 'b -> 'b是更通用的类型,如('a -> 'a) -> 'a -> 'a。但是你总是可以使用类型注释给编译器一个提示:
val c0 : ('a -> 'a) -> 'a -> 'a = fn f => fn x => x
val c1 : ('a -> 'a) -> 'a -> 'a = fn f => fn x => f x
...现在您的所有函数都应该具有相同的类型。