Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegant way to write ordered comparison on multiple properties

Say I have a data type like the following:

data Foo = Foo { field1, field2, field3 :: Int }

And I'd like to make it an instance of Ord by comparing field1, field2, and field3 in a particular order.

I find it very annoying to write:

-- (we need Eq Foo to define Ord Foo)
instance Eq Foo where
    x == y = all id [ f x == f y
                    | f <- [field1, field2, field3] ]

instance Ord Foo where
    compare x y = case (comparing field1) x y of
        EQ -> case (comparing field2) x y of
            EQ -> (comparing field3) x y
            ord -> ord
        ord -> ord

Monads like Maybe and Either have some really nice support for this kind of thing, and I find myself wishing that Ordering had something similar, e.g.

instance Ord Foo where
    compare == comparing field1 >>= comparing field2 >>= comparing field3

...or something like that.

I've needed to do this for complex data types, where re-ordering fields in the definition and depending on default definitions for deriving (Eq, Ord) were not possible, so I'm not interested in solutions that game the default instance declarations.

Is there a more elegant, or at least more terse, way to define this kind of ordering?

Thanks!

like image 838
koschei Avatar asked Apr 06 '14 21:04

koschei


1 Answers

You can use the Monoid instance for Ordering and functions to good effect here:

instance Ord Foo where
    compare = comparing field1 <> comparing field2 <> comparing field3

Another trick you can use that generalizes more readily to the Eq instance is to use the instances for tuples:

equating = on (==)
reorder v = (field1 v, field2 v, field3 v)

instance Eq  Foo where (==)    = equating  reorder
instance Ord Foo where compare = comparing reorder
like image 154
Daniel Wagner Avatar answered Nov 09 '22 19:11

Daniel Wagner