I've recently started reading "Haskell in Depth". The first part of the book covers the basics of the language. Here, among other cool things I've found an interesting example using two existing typeclasses (i.e.
Bounded) and two new typeclasses that seem to make a lot of sense (i.e.
Let's see that stuff in action. First of all, we need a type to play with:
data Direction = North | East | South | West deriving (Show, Enum, Bounded, BoundedEnum, Eq, CyclicEnum)
The instance of
Enum enables us to enumerate the values of type
Direction. In other words, we can move to the next value or previous value by using
pred. Not only that, by making
Direction enumerable, we can use the
main :: IO () main = do print $ succ North -- East print $ pred East -- North print [North ..] -- [North,East,South,West] print [East ..] -- [East,South,West] print [East .. South] -- [East,South]
Bounded allows us to generically call the lower-bound and upper-bound
main :: IO () main = do let allDirections :: [Direction] allDirections = [minBound .. maxBound] print allDirections -- [North,East,South,West]
Now, in Haskell an
Enum could be not
Bounded and the other way around. In case a type is both, it makes sense to define a
BoundedEnum typeclass and instance. That way, we can abstract the range
[minBound .. maxBound]:
class (Enum a, Bounded a) => BoundedEnum a where range :: [a] range = enumFrom minBound main :: IO () main = do let allDirections' :: [Direction] allDirections' = range print $ allDirections' -- [North,East,South,West]
There is still a problem though. In fact, the following raises a runtime exception:
main :: IO () main = do print $ pred North -- "tried to take `pred' of first tag in enumeration"
As a matter of fact, the enumeration does not wrap around and
North is the first value in the enum! We can solve that by defining a
CyclicEnum typeclass and instance:
class (Eq a, Enum a, Bounded a) => CyclicEnum a where cpred :: a -> a cpred d | d == minBound = maxBound | otherwise = pred d csucc :: a -> a csucc d | d == maxBound = minBound | otherwise = succ d main :: IO () main = do print $ cpred North -- West