为什么`succ i`在`i :: Num a => a`(而不是'Enum a`)中有效?

问题描述 投票:3回答:1

这似乎适用于GHCi和GHC。我将首先展示一个GHCi的例子。

鉴于i类型已被推断如下:

Prelude> i = 1
Prelude> :t i
i :: Num p => p

鉴于succEnum定义的函数:

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)和iNum 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没有出现在ix的类型签名中我认为这意味着GHC不会推断更多类型的my_succ类型。

但这似乎又错了:我用以下内容检查了这个想法(我第一次输入RankNTypes),显然GHC仍然推断Enum a

{-# LANGUAGE RankNTypes #-}

x :: forall a. Num a => a
x = 1
y = succ x

因此,似乎函数的推理规则比变量的推理规则更严格?

haskell types typeclass ghci
1个回答
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中的约束。如果是这样,默认aty
  • 单位类型()和列表类型[]被添加到标准类型列表的开头,这些类型在进行类型默认时尝试。

默认的默认类型列表(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可以是其他任何东西(即没有任何不适合这种类型的东西),但它可以用于较少的一般(更具体)类型:IntegerInt。即使他们中的许多人同时出现在表达中:

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 -> aforall a没有出现在ix的类型签名中。

那是一只红鲱鱼。我不确定为什么它会在一个案例中显示而不是在另一个案例中显示,但所有这些都在幕后有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。)

© www.soinside.com 2019 - 2024. All rights reserved.