这似乎同时适用于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
所以似乎函数的推理规则比变量的严格?
答案 0 :(得分:8)
是的,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
可以是 else 的任何内容(即不适合此类型的任何内容),但是可以将其用于不太通用(更具体)的类型: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
。)