编译器不会为多态常量值选择类型类

Compiler doesn't pick up typeclass for the polymorphic constant value

我是Haskell的新手,请多多包涵。

为什么以下 haskell 代码无法编译?

编译器似乎无法看到表达式 (maxBound :: a) 的类型是 a,它提供了一个 Enum 实例,而不是某些 type variable ‘a0’ambiguous.

class (Enum a, Bounded a) => SafeEnum a where
  ssucc :: a -> a
  ssucc x = if (fromEnum x) < (fromEnum (maxBound :: a)) then succ x else minBound

  spred :: a -> a
  spred x = if (fromEnum x) > (fromEnum (minBound :: a)) then pred x else maxBound
Stepik.hs:3:32: error:
    • Could not deduce (Enum a0) arising from a use of ‘fromEnum’
      from the context: SafeEnum a
        bound by the class declaration for ‘SafeEnum’
        at Stepik.hs:(1,1)-(6,82)
      The type variable ‘a0’ is ambiguous
      These potential instances exist:
        instance Enum Ordering -- Defined in ‘GHC.Enum’
        instance Enum Integer -- Defined in ‘GHC.Enum’
        instance Enum () -- Defined in ‘GHC.Enum’
        ...plus six others

您需要将此行添加到文件的顶部:

{-# LANGUAGE ScopedTypeVariables #-}

如果不启用此扩展,maxBound :: a 与 class 中引用的 a 不同。

本质上,在标准Haskell中,每个类型签名都有自己的类型变量,这些变量独立于任何其他变量。例如,这段代码

foo :: [a] -> Int
foo xs = length ys
   where
   ys :: [a]
   ys = xs

失败,因为ys :: [a]实际上意味着ys :: [b]有一个自变量b,而ys = xs不会产生一个[b].

启用扩展后,编译:

foo :: forall a . [a] -> Int
foo xs = length ys
   where
   ys :: [a]
   ys = xs

可以说,应该有不同的默认值,例如扩展应该默认打开。或者,当相同的 a 被使用两次时,GHC 应该提示打开扩展,因为这通常是问题所在。

默认情况下,即使类型变量的范围从 class 定义到 class 方法的类型签名(即 a 中的 [=14] =] 与 ssucc :: a -> a 中的 a 相同 a),它们 不是 从方法的类型签名到方法主体的范围,因此在函数 ssuccspred 的主体中的表达式 maxBound :: a 中, a 与类型签名中的 a 无关这些功能。

您可以启用 ScopedTypeVariables 扩展程序,如下所示:

{-# LANGUAGE ScopedTypeVariables #-}

之后 class 定义将进行类型检查。

请注意,如果您使用 forall 关键字,此扩展仅适用于 "normal" 函数声明。因此,在 class 定义之外,您需要启用此扩展 写入:

ssucc :: forall a. a -> a 
ssucc x = ... maxBound :: a ...

或者实际上:

ssucc :: forall a. (Enum a, Bounded a) => a -> a
ssucc x = ... maxBound :: a ...

但是 class 子句中的规则不同。

详情见the GHC docs