这似乎适用于GHCi和GHC。我将首先展示一个GHCi的例子。
鉴于i
类型已被推断如下:
Prelude> i = 1
Prelude> :t i
i :: Num p => p
鉴于succ
是Enum
定义的函数:
Prelude> :i Enum
class Enum a where
succ :: a -> a
pred :: a -> a
-- …OMITTED…
并且Num
不是Enum
的'子类'(如果我可以使用该术语):
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
-- …OMITTED…
为什么succ i
不会返回错误?
Prelude> succ i
2 -- works, no error
我希望:type i
可以被推断为:
Prelude> i = 1
Prelude> :type i
i :: (Enum p, Num p) => p
(我正在使用'GHC v.8.6.3')
加成:
在阅读了@RobinZigmond评论和@AlexeyRomanov回答后,我注意到1
可以被解释为众多类别中的一种,也可以解释为众多类别之一。感谢@AlexeyRomanov的回答,我对用于确定用于模糊表达的类型的默认规则了解得更多。
但是我不觉得Alexey的回答完全解决了我的问题。我的问题是关于i
的类型。这不是关于succ i
的类型。
这是关于succ
论证类型(Enum a
)和i
(Num a
)的表观类型之间的不匹配。
我现在开始意识到我的问题必须来自一个错误的假设:'一旦i
被推断为i :: Num a => a
,那么i
就没有其他了'。因此,我很困惑地看到succ i
被评估没有错误。
除了明确宣布的内容之外,GHC似乎也在推断Enum a
。
x :: Num a => a
x = 1
y = succ x -- works
但是,当类型变量显示为函数时,它不会添加Enum a
:
my_succ :: Num a => a -> a
my_succ z = succ z -- fails compilation
对我而言,附加到函数的类型约束似乎比应用于变量的类型更严格。
GHC说my_succ :: forall a. Num a => a -> a
和给予forall a
没有出现在i
和x
的类型签名中我认为这意味着GHC不会推断更多类型的my_succ
类型。
但这似乎又错了:我用以下内容检查了这个想法(我第一次输入RankNTypes),显然GHC仍然推断Enum a
:
{-# LANGUAGE RankNTypes #-}
x :: forall a. Num a => a
x = 1
y = succ x
因此,似乎函数的推理规则比变量的推理规则更严格?
是的,succ i
的类型是您所期望的推断:
Prelude> :t succ i
succ i :: (Enum a, Num a) => a
这种类型是模糊的,但它满足the defaulting rules GHCi的条件:
找到所有未解决的约束。然后:
- 找到那些形式为
(C a)
的地方,其中a
是一个类型变量,并将这些约束划分为共享一个共同类型变量a
的组。
在这种情况下,只有一个组:(Enum a, Num a)
。
- 仅保留至少一个类是交互式类的组(在下面定义)。
这个小组是保留的,因为Num
是一个互动课程。
- 现在,对于每个剩余的组G,依次从默认类型列表中尝试每种类型的
ty
;如果设置a = ty
将允许完全解决G中的约束。如果是这样,默认a
到ty
。- 单位类型
()
和列表类型[]
被添加到标准类型列表的开头,这些类型在进行类型默认时尝试。
默认的默认类型列表(sic)是(带有最后一个子句的补充)default ((), [], Integer, Double)
。
所以当你做Prelude> succ i
来实际评估这个表达式时(注意:t
不会评估它得到的表达式),a
被设置为Integer
(这个列表的第一个满足约束),结果打印为2
。
您可以通过更改默认值来查看原因:
Prelude> default (Double)
Prelude> succ 1
2.0
对于更新的问题:
我现在开始意识到我的问题必须来自一个错误的假设:'一旦
i
被推断为i :: Num a => a
,那么i
就没有其他了'。因此,我很困惑地看到succ i
被评估没有错误。
i
可以是其他任何东西(即没有任何不适合这种类型的东西),但它可以用于较少的一般(更具体)类型:Integer
,Int
。即使他们中的许多人同时出现在表达中:
Prelude> (i :: Double) ^ (i :: Integer)
1.0
这些用途不会影响i
本身的类型:它已经定义并且其类型是固定的。好的到目前为止?
好吧,添加约束也会使类型更具体,因此(Num a, Enum a) => a
比(Num a) => a
更具体:
Prelude> i :: (Num a, Enum a) => a
1
因为任何类型的a
满足(Num a, Enum a)
中的两个约束只满足Num a
。
但是,当类型变量显示为函数时,它不会添加
Enum a
:
那是因为你指定了一个不允许它的签名。如果你没有给出签名,就没有理由推断出Num
约束。但是例如
Prelude> f x = succ x + 1
将推断具有两个约束的类型:
Prelude> :t f
f :: (Num a, Enum a) => a -> a
因此,似乎函数的推理规则比变量的推理规则更严格?
实际上它是另一种方式,因为monomorphism restriction(默认情况下不在GHCi中)。你实际上有点幸运,不要在这里遇到它,但答案已经足够长了。搜索该术语应该给你解释。
GHC说
my_succ :: forall a. Num a => a -> a
和forall a
没有出现在i
和x
的类型签名中。
那是一只红鲱鱼。我不确定为什么它会在一个案例中显示而不是在另一个案例中显示,但所有这些都在幕后有forall a
:
Haskell type signatures are implicitly quantified.当使用语言选项
ExplicitForAll
时,关键字forall
允许我们确切地说出这意味着什么。例如:g :: b -> b
意思是:
g :: forall b. (b -> b)
(另外,你只需要ExplicitForAll
而不是RankNTypes
来记下forall a. Num a => a
。)