Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell toEnum on data type throws range exception

Tags:

haskell

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?

like image 584
dos Avatar asked Aug 22 '15 03:08

dos


2 Answers

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 succing the generated values until it succs 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 as Enum, the following should hold:

  • enumFrom and enumFromThen 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 6s with 1 + (fromEnum (maxBound :: Note)) to avoid accidentally getting the number wrong.

like image 58
Antal Spector-Zabusky Avatar answered Oct 20 '22 18:10

Antal Spector-Zabusky


[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.

like image 38
SingleNegationElimination Avatar answered Oct 20 '22 17:10

SingleNegationElimination