Anton Savelyev
Anton Savelyev

Reputation: 125

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

I'm new to Haskell, so forgive me in advance.

Why doesn't the following haskell code compile?

It seems like the compiler somehow fails to see that the type of the expression (maxBound :: a) is a which has an Enum instance provided, not some type variable ‘a0’ which is 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

Upvotes: 2

Views: 99

Answers (2)

K. A. Buhr
K. A. Buhr

Reputation: 51149

By default, even though type variables are scoped from the class being defined to the type signatures of the class's methods (i.e., the a in class SafeEnum a is the same a as the a in ssucc :: a -> a), they are not scoped from the type signatures of the methods to the method bodies, so in the expression maxBound :: a in the bodies of your functions ssucc and spred, the a has nothing to do with the a in the type signatures for those functions.

You can enable the ScopedTypeVariables extension, like so:

{-# LANGUAGE ScopedTypeVariables #-}

after which the class definition will type check.

Note that this extension only applies to "normal" function declarations if you use the forall keyword. So, outside of a class definition, you'd need to enable this extension and write:

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

or actually:

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

but the rules are different inside a class clause.

See the GHC docs for details.

Upvotes: 5

chi
chi

Reputation: 116174

You need to add this line to the top of your file:

{-# LANGUAGE ScopedTypeVariables #-}

Without this extension turned on, maxBound :: a does not refer to the same a as in the class.

Essentially, in standard Haskell, each type signature has its own type variables, which are independent from any other variable. For instance, this code

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

fails, since ys :: [a] really means ys :: [b] with an independent variable b, and ys = xs does not produce a [b].

With the extension turned on, this compiles:

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

Arguably, there should be a different default, e.g. the extension should be on by default. Alternatively, GHC should hint at turning the extension on when the same a is used twice, since often that's the issue.

Upvotes: 2

Related Questions