Is it possible to have a typeclass imply another typeclass in Haskell? For example let's say there is a bunch of "things" that can be ordered by an "attribute":

data Person = Person { name :: String, age :: Int }

Person p1 <= Person p1 = (age p1) <= (age p2)

To avoid repetition one could define a "orderable by key" type class

class OrdByKey o where
  orderKey :: (Ord r) => o -> r
  x <= y = (orderKey x) <= (orderKey y)

Then the instance declaration for Person could look like this

instance OrdByKey Person where
  orderKey Person p = age p

Now this does obviously not work for multiple reasons. I wonder if it's possible at all?

  • 2
    If your goal is just to order based on a specific field in your record, you could always just define the instance of Ord for Person to compare that particular field. – sabauma Feb 8 '13 at 16:52
  • @sabauma yeah but I'm looking for a generalization of that "can be compared by some key" thing. – Jonas H. Feb 8 '13 at 16:57
  • For this particular example, take a look at the comparing function: comparing age you me – Michael Steele Feb 9 '13 at 0:39
  • For a more general operator then comparing you could take a look at on. It has type (b -> b -> c) -> (a -> b) -> a -> a -> c, so comparing age = on compare age but you could also make on (==) f to test two things for equality after applying f. – Laar Feb 9 '13 at 13:40

As you have specified it, the OrdByKey class can only have one instance per type, when it sounds like you would like to be able to declare an instance for each field in your record type.

To accomplish that, you will have to put the field type into the class definition as well. This lets you do something like the following:

{-# LANGUAGE MultiParamTypeClasses #-}


class (Ord r) => OrdByKey o r where
   orderKey :: o -> r

instance OrdByKey Person Int where
  orderKey p = age p

x <=? y = (orderKey x :: Int) <= (orderKey y :: Int)

However, you can only have one instance per field type, so if your Person type looks like


you will not be able to have a version to compare on both the name and the ssn fields. You could get around this by wrapping each field in a newtype so each field has a unique type. So your Person type would look like

data Person = Person { name :: Name, age :: Age, ssn :: SSN}

That would lead to a lot of newtypes floating around though.

The real downside of this is the need to specify the return type for the orderKey function. I would recommend using the on function from Data.Function to write the appropriate comparison functions. I think a function like

compareByKey :: (Ord b) => (a -> b) -> a -> a -> Bool
compareByKey = on (<=)

generalizes your idea of "can be compared by some key". You just have to give it the function that extracts that key, which would be exactly the accessor functions for your Person type, in this case.

I can't think of an instance where the OrdByKey class would be useful and trying to overload the <= with multiple versions for the same type seems like it would be down right confusing in practice.

  • Looks like using multi-param type classes is the clean way to do it. Thanks! – Jonas H. Feb 9 '13 at 10:52

You could do this:

instance Ord Person where
    compare p1 p2 = compare (age p1) (age p2)

Now the standard <= operator will work on Persons and compare their ages.

Your Answer

