Reputation: 125
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
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
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