Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isnt enum typeclass a subclass of ord typeclass?

I would like to know the reasons why enum typeclass does not inherit the ord typeclass. The succ function in enum essentially imposes an order on the values and yet ord is not a required constraint.

Edit: Also, for other functions in the typeclass, there seems to be an implicit assumption about ord operators being defined for them. As shown here https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#v:enumFromTo

a possible implementation being enumFromTo n m | n <= m = n : enumFromTo (succ n) m | otherwise = []

like image 359
jude Avatar asked Nov 06 '22 12:11

jude


1 Answers

The Ord typeclass is meant to represent total orders, which among other things requires transitivity: if a < b and b < c, then a < c.

A superclass should be necessary for the definition of an instance of a class.

An Enum, though it might impose an order (though not a necessarily total one) on its elements, doesn't use or require a total ordering.

As an example, consider everyone's favorite decision-making algorithm:

data RPS = Rock | Paper | Scissors deriving (Eq, Ord)

instance Enum RPS where
    succ Rock = Paper
    succ Paper = Scissors
    succ Scissors = Rock

The Ord instance derived from the definition is total, and straightforward: Rock is the smallest element, Scissors the largest, and Paper comes between.

The order implied by this enumeration isn't total. Given succ, you might assume that Rock < Paper and Paper < Scissors, but then Rock < Scissors should be true, which is not implied by succ Scissors = Rock.

The Ord instance wouldn't be useful in defining the Enum shown above.


Unfortunately, a proper instance declaration should use toEnum and fromEnum, from which all the other methods (including succ) can be derived. This makes the definition a little more verbose. Following the model of DaysOfWeek

instance Enum RPS where
    -- *We* know that n `mod` 3 has 3 possible results, but
    -- the exhaustivity checker does not, so use _ in place of 2
    toEnum n = case n `mod` 3 of
                  0 -> Rock
                  1 -> Paper
                  _ -> Scissors

    fromEnum Rock = 0
    fromEnum Paper = 1
    fromEnum Scissors = 2

    -- The generated definitions don't handle wrapping. Without these,
    -- [Paper..Rock] would produce [], not [Paper, Scissors, Rock].
    enumFromTo wd1 wd2
            | wd1 == wd2 = [wd1]
    enumFromTo wd1 wd2 = wd1 : enumFromTo (succ wd1) wd2
    enumFromThenTo wd1 wd2 wd3
            | wd2 == wd3 = [wd1, wd2]
    enumFromThenTo wd1 wd2 wd3 = wd1 : enumFromThenTo wd2 (toEnum $ (2 * fromEnum wd2) - (fromEnum wd1)) wd3
like image 78
chepner Avatar answered Nov 15 '22 06:11

chepner