很好。
S :: NaturalNumber -> NaturalNumber
In
infinity = S infinity
我们从零开始:我们将无穷大赋予一些未解决的类型“_a”,并尝试找出它是什么。我们知道我们已经将“S”应用于“infinity”,所以“_a”必须是构造函数类型中箭头左侧的任何内容,即“NaturalNumber”。我们知道“infinity”是“S”的应用结果,因此“infinity :: NaturalNumber”,同样地(如果我们为此定义得到两个冲突的类型,则必须发出类型错误)。
相似的推理适用于相互递归的定义。 “inf1”必须是“NatrualNumber”,因为它在“inf2”中作为参数出现在“S”中;“inf2”必须是“NatrualNumber”,因为它是“S”的结果;等等。
一般算法是将定义的未知类型分配给它们(特别说明的例外是字面量和构造函数),然后通过查看如何使用每个定义来创建对这些类型的约束条件。例如,这必须是某种列表,因为它正在被“reverse”,这必须是一个“Int”,因为它正在被用来从“IntMap”中查找值,等等。
在这种情况下,
oops = S 'a'
'a' :: Char
是一个字面量,但是我们还需要 'a' :: NaturalNumber
因为它作为 S
的参数。这导致了一个明显的错误约束:字面量的类型既必须是 Char
又必须是 NaturalNumber
,从而导致类型错误。
在...
bottom = bottom
我们从
bottom :: _a
开始。唯一的限制是
_a ~ _a
,因为在定义
bottom
的 RHS 上期望一个类型为
_a
的值(
bottom
)被使用。由于没有进一步约束类型,未解决的类型变量被
泛化:它通过普遍量化被绑定,生成
bottom :: forall a. a
。
请注意,在推断
bottom
的类型时,上述两个使用
bottom
的位置都具有相同的类型(
_a
)。这破坏了多态递归:在定义内部每次出现的值的每个实例都被视为与定义本身相同的类型。例如:
data Binary a = Leaf a | Branch (Binary (a, a))
headB (Leaf x) = x
headB (Branch bin) = fst (headB bin)
所以您需要一个类型签名:
headB :: Binary a -> a
headB (Leaf x) = x
headB (Branch bin) = fst (headB bin)
当某个东西没有类型签名时,类型检查器会尝试为其分配一个类型。如果在此过程中遇到了虚假的约束条件,它会拒绝程序。当某个东西有一个类型签名时,类型检查器会进入其中以确保它是正确的(如果你更喜欢这样想,它会尝试证明它是错误的)。
Haskell的类型系统不是图灵完备的,因为有严格的语法限制以防止例如类型lambda(没有语言扩展),但这并不足以确保所有程序都能无误地完成运行,因为它仍然允许底部(更不用说所有不安全的函数了)。它提供了一个较弱的保证,即如果一个程序在不使用不安全函数的情况下运行完成,它将保持类型正确。在GHC下,通过足够的语言扩展,类型系统确实变得图灵完备。我认为它不允许出现类型不正确的程序;最多只能使编译器陷入无限循环。
infinity
时,Haskell 将尝试合成infinity :: NaturalNumber
(因为它是使用S :: NaturalNumber -> NaturalNumber
构造的),然后检查infinity :: NaturalNumber
(因为infinity
是给定给S
的参数)。 - Alec