Chris Martin
Chris Martin

Reputation: 30736

How do we concisely define Eq based on a function?

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

Answers (1)

Antal Spector-Zabusky
Antal Spector-Zabusky

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

Related Questions