4

This code works, but it's verbose, and I'm sure there's a more concise way.

import qualified Data.Vector as V

data Event a = Event { start :: Time
                     , duration :: Time
                     , payload :: Maybe a } deriving (Show, Eq)

instance Ord a => Ord (Event a) where
  (<=) a b = start a < start b
    || start a == start b && duration a < duration b
    || start a == start b && duration a == duration b && payload a <= payload b

The idea behind it is that if one thing starts before the other, you should call it the lesser, and don't even look at the other two fields. Similarly, if they start at the same time but one is more brief, then that briefer one is the lesser, and you can ignore the third field.

Jeffrey Benjamin Brown
  • 3,427
  • 2
  • 28
  • 40

3 Answers3

6

Use deriving:

data Event a = Event { start :: Time
                     , duration :: Time
                     , payload :: Maybe a } deriving (Show, Eq, Ord)

The derived instance is automatically lexicographic.

HTNW
  • 27,182
  • 1
  • 32
  • 60
4

As @HTNW notes, the automatically derived Ord instance will work. In more general situations, if you need to sort lexicographically on multiple items each with existing Ord instances, you can use the automatic tuple Ord instance:

instance Ord a => Ord (Event a) where
  (<=) a b = order a <= order b
    where order x = (start x, duration x, payload x)
K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71
  • 4
    If you want to save keystrokes, you can use `Data.Function.on`: ``(<=) = (<=) `on` order``. You can also make it a one (well, two)-liner: `(<=) = on (<=) $ (,,) <$> start <*> duration <*> payload` – HTNW Feb 11 '18 at 04:15
3

As HTNW suggests, use deriving if you can. If you can't (e.g. the record fields aren't in the appropriate order, or you aren't actually writing an instance), a very nice option is doing it in terms of compare (or comparing, which adds extra convenience in your case) rather than (<=) and then taking advantage of the Monoid instance for Ordering, which amounts to lexicographic order:

import Data.Ord (comparing)
import Data.Monoid ((<>))

instance Ord a => Ord (Event a) where
    compare a b = comparing start a b
        <> comparing duration a b
        <> comparing payload a b

As functions have a Monoid instance which acts on the results, you might take it even further:

instance Ord a => Ord (Event a) where
    compare = comparing start <> comparing duration <> comparing payload
duplode
  • 33,731
  • 7
  • 79
  • 150
  • Nice. Now I feel foolish for having written `applyCmp f x y = mconcat (f <*> pure x <*> pure y)` a week ago. – Cirdec Feb 11 '18 at 04:05