首先,我们可以从查看变量开始。这些是x、y 和z。我们首先为它们分配“通用类型”,所以:
x :: a
y :: b
z :: c
现在我们看一下表达式的右手边,看到(y z)。这意味着我们以z 作为参数“调用”y。因此,我们将y 的类型“特化”为y :: c -> d。那么(y z) 的类型就是(y z) :: d。现在我们看到x (y z)。因此,我们再次将x 的类型“特化”为x :: d -> e。结果我们得到:
x :: d -> e
y :: c -> d
z :: c
最后,lambda 表达式映射 \x y z -> x (y z)。所以这意味着我们在“伪代码”中寻找结果类型:type(x) -> type(y) -> type(z) -> type('x (y z)')。或者:
\x y z -> x (y z) :: (d -> e) -> (c -> d) -> c -> e
对于第二个表达式,我们首先要导出(<)的类型:
Prelude> :t (<)
(<) :: Ord a => a -> a -> Bool
这是因为(<) 在Prelude 中以该类型定义。
现在我们知道了,我们可以看看类型签名。我们将首先假设x :: b 的类型。现在我们看右边,看到我们调用x (<)。这意味着我们知道x 具有Ord a => (a -> a -> Bool) -> c 类型,并且我们知道x (<) :: c。然后我们又得到了一个 lambda 表达式,正如我们在上面看到的,我们可以像这样解决它:
(\x -> x (<)) :: Ord a => ((a -> a -> Bool) -> c) -> c
最后对于第三个表达式,我们先重写为:
(.) (.) (.)
这里的first(.)函数,原来是.(不带括号),但我们会先去掉操作符的语法糖。
接下来我们会给操作符起一个名字(只是为了方便我们给它们命名)。这在 Haskell 中是不允许的,但我们现在将忽略它。我们这样做的原因是为了稍后引用特定(.)函数的类型:
(.<i>1</i>) (.<i>2</i>) (.<i>3</i>)
接下来我们查找(.)的类型:
Prelude> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
所以我们首先分配一些类型:
(.1) :: (b -> c) -> (a -> b) -> a -> c
(.2) :: (e -> f) -> (d -> e) -> d -> f
(.3) :: (h -> i) -> (g -> h) -> g -> i
现在我们需要进一步分析这些类型。我们用(.2) 作为参数调用(.1),所以我们知道(b -> c)((.1) 的第一个参数等价于(e -> f) -> (d -> e) -> d -> f。所以这意味着:
(b -> c) ~ (e -> f) -> (d -> e) -> d -> f
由于类型箭头是右关联,(e -> f) -> (d -> e) -> d -> f 实际上意味着(e -> f) -> ((d -> e) -> (d -> f))。所以现在我们可以做一个分析:
b -> c
~ (e -> f) -> ((d -> e) -> (d -> f))
这意味着b ~ (e -> f) 和c ~ ((d -> e) -> (d -> f))。所以我们将我们的第一个(.1) :: (b -> c) -> (a -> b) -> a -> c“专门化”到
(.1) :: ((e -> f) -> ((d -> e) -> (d -> f))) -> (a -> (e -> f)) -> a -> ((d -> e) -> (d -> f))
现在(.1) (.2)的类型是
(.1) (.2) :: (a -> (e -> f)) -> a -> ((d -> e) -> (d -> f))
但是现在我们用(.3) 调用该函数,所以我们必须派生((.1) (.2)) (.3) 的类型。因此,我们必须再次进行类型解析:
a -> (e -> f)
~ (h -> i) -> ((g -> h) -> (g -> i))
这意味着a ~ (h -> i)、e ~ (g -> h) 和f ~ (g -> i)。所以现在我们已经将类型解析为:
((.1) (.2)) (.3) :: a -> c
= (h -> i) -> ((d -> (g -> h)) -> (d -> (g -> i)))
= (h -> i) -> (d -> g -> h) -> d -> g -> i
最后一行只是语法上的简化。如您所见,这映射到我们从ghci 派生的类型。