-- An intuitive way to think about this is in terms of tables. Given datatypes
--
-- @
-- data X = A | B | C | D deriving ('Bounded', 'Enum', 'Eq', 'Ord', 'Show')
-- data Y = E | F | G deriving ('Bounded', 'Enum', 'Eq', 'Ord', 'Show')
-- @
--
-- we can form the table
--
-- @
-- (A, E) (A, F) (A, G)
-- (B, E) (B, F) (B, G)
-- (C, E) (C, F) (C, G)
-- (D, E) (D, F) (D, G)
-- @
--
-- in a natural lexicographical order. We simply require that there be a finite
-- number of columns, and allow an unbounded number of rows (in so far as the
-- lazy evaluation mechanism allows them). In even more practical terms, we require
-- a finite number of columns because we use that number to perform arithmetic.
instance ( Bounded b
, Enum a
, Enum b
) => Enum (a, b) where
toEnum k = let n = 1 + fromEnum (maxBound :: b) -- Enums are 0 indexed, but we want to
a = toEnum ((k `div` n)) -- divide by the number of elements in a row to find the row and
b = toEnum ((k `mod` n)) -- get the remainder to find the column.
in (a,b)
fromEnum (a, b) = let n = 1 + fromEnum (maxBound :: b)
i = fromEnum a
j = fromEnum b
in (i*n + j)
-- | This instance of 'Enum' is defined in terms of the previous instance. We
-- use the natural equivalence of the types @(a,b,c)@ and @(a,(b,c))@ and use
-- the previous definition. Again, notice that all elements but the first must
-- be bounded.
instance ( Bounded b
, Bounded c
, Enum a
, Enum b
, Enum c
) => Enum (a, b, c) where
fromEnum (a, b, c) = fromEnum (a, (b,c))
toEnum k = let (a, (b, c)) = toEnum k
in (a, b, c)
Lexicographical. Dictionary order.