I am trying to write an instance of Enum for the following type:
-- Type declarations:
-- Octave
data Octave =
O1 | O2 | O3
deriving (Show, Read, Eq, Ord, Bounded, Enum)
-- Note
data Note =
A | B | C | D | E | F
deriving (Show, Read, Eq, Ord, Bounded, Enum)
-- Pitch
data Pitch = Pitch Octave Note
deriving (Show, Eq, Ord)
-- Why doesn't this work?
instance Enum Pitch where
fromEnum (Pitch o n) = (fromEnum o)*6 + (fromEnum n)
toEnum x = (Pitch o n)
where
o = toEnum (x `div` 6)
n = toEnum (x `mod` 6)
This works fine for:
[(Pitch O1 A) .. (Pitch O3 F)]
But fails for:
[(Pitch O1 A) .. ]
With the error:
*** Exception: toEnum{Octave}: tag (3) is outside of enumeration's range (0,2)
I understand the error. My questions are: How to correctly write the Enum instance to perform this enumeration? Is it possible? Most of all: Is it good practice?
Your problem is that Pitch
is implicitly bounded – i.e., it has a least and greatest element – but you don't reflect that boundedness in your code. The code
[Pitch O1 A ..]
desugars to
enumFrom (Pitch O1 A)
which keeps succ
ing the generated values until it succ
s Pitch O3 F
and blows up. How could it know that it should stop there?
From the Prelude documentation:
For any type that is an instance of class
Bounded
as well asEnum
, the following should hold:…
enumFrom
andenumFromThen
should be defined with an implicit bound, thus:enumFrom x = enumFromTo x maxBound enumFromThen x y = enumFromThenTo x y bound where bound | fromEnum y >= fromEnum x = maxBound | otherwise = minBound
Thus, to fix this problem, just add
instance Bounded Pitch where
minBound = Pitch minBound minBound
maxBound = Pitch maxBound maxBound
and then add the code from the documentation:
instance Enum Pitch where
-- ...
enumFrom x = enumFromTo x maxBound
enumFromThen x y = enumFromThenTo x y bound
where
bound | fromEnum y >= fromEnum x = maxBound
| otherwise = minBound
and now [Pitch O1 A ..] will stop at the end:
λ> [Pitch O1 A ..]
[Pitch O1 A,Pitch O1 B,Pitch O1 C,Pitch O1 D,Pitch O1 E,Pitch O1 F,Pitch O2 A,Pitch O2 B,Pitch O2 C,Pitch O2 D,Pitch O2 E,Pitch O2 F,Pitch O3 A,Pitch O3 B,Pitch O3 C,Pitch O3 D,Pitch O3 E,Pitch O3 F]
Side note: you can replace separate calls to div
and mod
with pattern-matching on a single call to divMod
: x `divMod` y == (x `div` y, x `mod` y)
. (For strictly positive numbers, like these, I believe I've heard that quotRem
may be a better choice; quot
and rem
are like div
and mod
, but with different sign-related behaviors.) Additionally, you could replace your 6
s with 1 + (fromEnum (maxBound :: Note))
to avoid accidentally getting the number wrong.
[x ..]
is desugared into enumFrom x
method, for which the prelude provides the following default implementation:
enumFrom x = map toEnum [fromEnum x ..]
Override it to do the right thing by your instance. You'll also likely want to override enumFromThen
for similar reasons.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With