所有
我想在ML中导出下面函数的类型表达式:
fun f x y z = y (x z)
现在我知道键入相同内容会生成类型表达式。但我希望手工推导出这些价值观。
另外,请提及在派生类型表达式时要遵循的一般步骤。
答案 0 :(得分:8)
我将尽可能以最机械的方式做到这一点,就像大多数编译器的实现一样。
让我们分解一下:
fun f x y z = y (x z)
这基本上是糖:
val f = fn x => fn y => fn z => y (x z)
让我们添加一些元语法类型变量(这些不是真正的SML类型,只是为了这个例子的占位符):
val f : TX = fn (x : T2) => fn (y : T3) => fn (z : T4) => y (x z) : T5
好的,我们可以从这开始生成一个约束系统。 T5是f的最终返回类型。目前,我们只是调用整个函数“TX”的最终类型 - 一些新鲜的未知类型变量。
因此,在您给出的示例中将要生成约束的是函数应用程序。它告诉我们表达式中事物的类型。事实上,这是我们唯一的信息!
那么应用程序告诉我们什么?
忽略我们上面指定的类型变量,让我们看一下函数的主体:
y (x z)
z不适用于任何东西,所以我们只是查看我们分配给它的类型变量是早些时候(T4)并使用它作为它的类型。
x应用于z,但我们还不知道它的返回类型,所以让我们为它生成一个新的类型变量,并使用我们之前分配的类型x(T2)来创建约束:
T2 = T4 -> T7
y应用于(x z)的结果,我们称之为T7。再一次,我们还不知道y的返回类型,所以我们只给它一个新的变量:
T3 = T7 -> T8
我们也知道y的返回类型是函数整体的返回类型,我们之前称之为“T5”,所以我们添加约束:
T5 = T8
为了紧凑性,我将稍微解决这个问题,并根据函数返回函数的事实为TX添加约束。这可以通过完全相同的方法推导出来,除了它有点复杂。如果你不相信我们最终会遇到这种限制,你希望自己可以自己做这个练习:
TX = T2 -> T3 -> T4 -> T5
现在我们收集所有约束条件:
val f : TX = fn (x : T2) => fn (y : T3) => fn (z : T4) => y (x z) : T5
TX = T2 -> T3 -> T4 -> T5
T2 = T4 -> T7
T3 = T7 -> T8
T5 = T8
我们开始通过在约束系统中用右手边代替左手边来解决这个方程组,并在原始表达式中,从最后一个约束开始并一直工作到顶部。
val f : TX = fn (x : T2) => fn (y : T3) => fn (z : T4) => y (x z) : T8
TX = T2 -> T3 -> T4 -> T8
T2 = T4 -> T7
T3 = T7 -> T8
val f : TX = fn (x : T2) => fn (y : T7 -> T8) => fn (z : T4) => y (x z) : T8
TX = T2 -> (T7 -> T8) -> T4 -> T8
T2 = T4 -> T7
val f : TX = fn (x : T4 -> T7) => fn (y : T7 -> T8) => fn (z : T4) => y (x z) : T8
TX = (T4 -> T7) -> (T7 -> T8) -> T4 -> T8
val f : (T4 -> T7) -> (T7 -> T8) -> T4 -> T8 = fn (x : T4 -> T7) => fn (y : T7 -> T8) => fn (z : T4) => y (x z) : T8
好的,所以这一刻看起来很可怕。我们现在并不需要整个表达式 - 只是在那里提供一些清晰的解释。基本上在符号表中我们会有这样的东西:
val f : (T4 -> T7) -> (T7 -> T8) -> T4 -> T8
最后一步是将剩下的所有类型变量概括为我们熟悉和喜爱的更熟悉的多态类型。基本上这只是一个传递,将第一个未绑定的类型变量替换为'a,将第二个替换为'b,依此类推。
val f : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
我非常确定你会发现你的SML编译器也会为该术语提供的类型。我是通过手工和内存完成的,所以如果我在某个地方拙劣的话,我会道歉:p
我发现很难找到对此推理和类型约束过程的良好解释。我用两本书来学习它 - 安德鲁·阿佩尔的“现代编译器实现ML”和皮尔斯的“类型和编程语言”。对我来说,两个人都没有独立完全照亮,但在他们两个之间我想出来了。
答案 1 :(得分:3)
确定您需要查看每个使用它的地方的类型。例如,如果您看到val h = hd l
,则表示l
是一个列表(因为hd
将列表作为参数)并且您还知道h
的类型是l
列表的类型。因此,假设h
的类型为a
,l
的类型为a list
(其中a
为占位符)。现在,如果您看到val h2 = h*2
,则知道h
和h2
是int
,因为2
是一个int,您可以将int与另一个int相乘并且两个整数相乘的结果是int。由于我们之前说h
的类型为a
,这意味着a
为int
,因此l
的类型为int list
。< / p>
让我们解决你的问题:
让我们按照评估顺序考虑表达式:首先,您执行x z
,即将x
应用于z
。这意味着x
是一个函数,因此它具有类型a -> b
。由于z
作为函数的参数给出,因此它必须具有类型a
。 x z
的类型因此b
,因为这是x
的结果类型。
现在调用y
,结果为x z
。这意味着y
也是一个函数,其参数类型是x
的结果类型,即b
。因此y
的类型为b -> c
。同样,表达式y (x z)
的类型对此c
也是如此,因为这是y
的结果类型。
由于这些是函数中的所有表达式,因此我们无法进一步限制类型,因此x
,y
和z
的最常见类型为'a -> 'b
,'b -> 'c
和'a
,整个表达式的类型为'c
。
这意味着f
的整体类型为('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
有关如何以编程方式推断类型的解释,请参阅Hindley–Milner type inference。
答案 2 :(得分:0)
解释类型推断的另一种方法是为每个(子) - 表达式和每个(子) - 模式分配一个类型变量。
然后,程序中的每个构造都有一个关联那些与该构造相关的类型变量的等式。
例如,如果程序包含f x 'a1是f的类型变量,'a2是x的类型变量,'a3是“f x”的类型变量,
然后应用程序导致类型方程: 'a1 ='a2 - &gt; “A3
然后,类型推断基本上涉及为声明求解一组类型方程。对于ML来说,只需使用统一即可完成,并且手动操作非常简单。