这似乎适用于 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 的回答,我对用于决定用于模糊表达式的类型的默认规则有了更多的了解。
但是,我不认为阿列克谢的回答完全解决了我的问题。我的问题是关于
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
这种类型是有歧义的,但是满足GHCi的the defaulting rules中的条件:
在这种情况下,只有一组:
(Enum a, Num a)
。保留这个组,因为
Num
是一个交互式类。默认的默认类型列表 (sic) 是(添加了最后一个子句)
default ((), [], Integer, Double)
。因此,当您执行
Prelude> succ i
来实际评估此表达式时(注意 :t
不评估它获得的表达式), a
设置为 Integer
(此列表中满足约束的第一个),结果打印为 2
。您可以通过更改默认值来查看原因:
Prelude> default (Double)
Prelude> succ 1
2.0
对于更新的问题:
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
。那是因为您指定了一个不允许它的签名。如果您不提供签名,则没有理由推断
Num
约束。但是例如Prelude> f x = succ x + 1
将推断具有两个约束的类型:
Prelude> :t f
f :: (Num a, Enum a) => a -> a
由于 monomorphism restriction(默认情况下不在 GHCi 中),它实际上是相反的。你没有在这里遇到它实际上有点幸运,但答案已经足够长了。搜索这个词应该会给你解释。
那是红鲱鱼。我不确定为什么它在一种情况下而不是另一种情况下显示,但所有这些都在幕后使用
forall a
:(另外,你只需要
ExplicitForAll
而不是 RankNTypes
来写下 forall a. Num a => a
。)关于haskell - 为什么 `succ i` 在 `i::Num a => a` (而不是 `Enum a` )中有效?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55308208/