为什么“ succ i”在“ i :: Num a => a”(而不是“ Enum a`”)的地方有效?

时间:2019-03-22 21:46:10

标签: haskell types typeclass ghci

这似乎同时适用于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不会同时出现在ix的类型签名中,我认为这意味着GHC不会为my_succ类型推断出更多的类。

但这似乎又是错误的:我已经用以下方法(第一次输入RankNTypes)检查了这个想法,显然GHC仍在推断Enum a

{-# LANGUAGE RankNTypes #-}

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

所以似乎函数的推理规则比变量的严格?

1 个答案:

答案 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 的任何内容(即不适合此类型的任何内容),但是可以将其用于不太通用(更具体)的类型: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 -> a,并且给定的forall 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。)