Reputation: 30736
We can use Data.Ord.comparing
to write nicely concise Ord
instances:
comparing :: Ord a => (b -> a) -> b -> b -> Ordering
comparing p x y = compare (p x) (p y)
f :: b -> a
instance Ord a => Ord b where compare = comparing f
I would expect to find some similar function that would help to define Eq
instances:
-- Does something like this exist?
equalityOn :: Eq a => (b -> a) -> b -> b -> Bool
equalityOn p a b = p a == p b
instance Eq a => Eq b where (==) = equalityOn f
Hoogle doesn't turn up anything with this signature, so I'm guessing this isn't a common way to define Eq
. Obviously this is not a difficult problem to solve, but what is the idiomatic approach?
Upvotes: 2
Views: 90
Reputation: 36622
The way this is done is to reproduce your equalityOn
function inline, using Data.Function.on
, which has the type
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
This function generalizes comparing
:
(.*.) `on` f = \x y -> f x .*. f y
(which is literally the implementation from base). Thus, we have comparing = (compare `on`)
; on
lifts any binary function by applying it to the results of a unary function.
So equalityOn
is just ((==) `on`)
, and your example instance becomes
import Data.Function
instance Eq A => Eq B where
(==) = (==) `on` f
The proposal to add an equating :: Eq a => (b -> a) -> b -> b -> Bool
(the more common name for your equalityOn
) apparently comes up periodically; here's an example thread from July 2014. The general consensus seems to be (repeatedly) that ((==) `on`)
is preferable instead, although not necessarily by a huge margin. Indeed, apparently comparing
predates on
, and I recall once seeing someone argue that if the library were designed today, we would have neither comparing
nor equating
and just use on
everywhere! But: your mileage may vary :-) (I'm staying neutral here.)
Upvotes: 9