让我们系统地拼出类型部分。我们将从uncurry 和($) 的类型开始:
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
由于目标表达式有 ($) 作为 uncurry 的参数,让我们排列它们的类型以反映这一点:
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
($) 的整个类型与uncurry 的第一个参数类型对齐,($) 的参数和结果类型与uncurry 的第一个参数类型对齐,如图所示。这是对应关系:
uncurry's a <==> ($)'s a -> b
uncurry's b <==> ($)'s a
uncurry's c <==> ($)'s b
这有点令人困惑,因为一种类型中的a 和b 类型变量与另一种类型不同(就像plusTwo x = x + 2 中的x 与x 不同一样在timesTwo x = x * 2)。但是我们可以重写类型来帮助解释这一点。在像这样的简单 Haskell 类型签名中,任何时候你看到一个类型变量,你都可以用任何其他类型替换它的所有出现,也可以得到一个有效类型。如果你选择 fresh 类型变量(类型变量不会出现在原始的任何地方),你会得到一个 equivalent 类型(可以转换回原来的类型) ;如果您选择非新鲜类型,您会得到一个专业版本的原始版本,它适用于更窄范围的类型。
但无论如何,让我们将它应用到uncurry:: 的类型上
-- Substitute a ==> x, b ==> y, c ==> z:
uncurry :: (x -> y -> z) -> (x, y) -> z
让我们用重写后的类型重做“排队”:
uncurry :: (x -> y -> z) -> (x, y) -> z
($) :: (a -> b) -> a -> b
现在很明显:x <==> a -> b、y <==> a 和 z <==> b。现在,将uncurry 的类型变量替换为($) 中的对应类型,我们得到:
uncurry :: ((a -> b) -> a -> b) -> (a -> b, a) -> b
($) :: (a -> b) -> a -> b
最后:
uncurry ($) :: (a -> b, a) -> b
这就是您确定类型的方式。它的作用如何?好吧,在这种情况下,最好的方法是查看类型并仔细考虑,弄清楚我们必须编写什么才能获得该类型的函数。让我们这样重写它,让它更神秘:
mystery :: (a -> b, a) -> b
mystery = ...
既然我们知道mystery 是一个参数的函数,我们可以扩展这个定义来反映:
mystery x = ...
我们也知道它的参数是一对,所以我们可以再扩展一点:
mystery (x, y) = ...
既然我们知道x 是一个函数,而y :: a,我喜欢使用f 来表示“函数”并将变量命名为与其类型相同——这有助于我推理函数,所以让我们这样做:
mystery (f, a) = ...
现在,我们在右手边放什么?我们知道它必须是b 类型,但我们不知道b 是什么类型(它实际上是调用者选择的任何类型,所以我们无法知道)。所以我们必须以某种方式使用我们的函数f :: a -> b 和值a :: a 创建一个b。啊哈!我们可以只调用带有值的函数:
mystery (f, a) = f a
我们在没有查看uncurry ($) 的情况下编写了这个函数,但事实证明它与uncurry ($) 做的事情相同,我们可以证明这一点。先从uncurry和($)的定义说起:
uncurry f (a, b) = f a b
f $ a = f a
现在,用equals代替equals:
uncurry ($) (f, a) = ($) f a -- definition of uncurry, left to right
= f $ a -- Haskell syntax rule
= f a -- definition of ($), left to right
= mystery (f, a) -- definition of mystery, right to left
因此,在 Haskell 中攻击您不理解的类型的一种方法是尝试编写一些具有该类型的代码。 Haskell 与其他语言的不同之处在于,这通常是比尝试阅读代码更好的策略。