`Comparison` could work for any monoid not just Ordering
safareli opened this issue · comments
-- | An adaptor allowing `>$<` to map over the inputs of a comparison function.
newtype Comparison o a = Comparison (a -> a -> o)
derive instance newtypeComparison :: Newtype (Comparison o a) _
instance contravariantComparison :: Contravariant (Comparison o) where
cmap f (Comparison g) = Comparison (g `on` f)
instance semigroupComparison :: Semigroup o => Semigroup (Comparison o a) where
append (Comparison p) (Comparison q) = Comparison (p <> q)
instance monoidComparison :: Monoid o => Monoid (Comparison o a) where
mempty = Comparison (\_ _ -> mempty) -- mempty of `Ordering` is `EQ`
-- | The default comparison for any values with an `Ord` instance.
defaultComparison :: forall a. Ord a => Comparison Ordering a
defaultComparison = Comparison compare
we can do something like this
newtype On m a = On (a -> a -> m)
derive instance newtypeEquivalence :: Newtype (On m a) _
instance contravariantEquivalence :: Contravariant (On m) where
cmap f (On g) = On (g `on` f)
instance semigroupEquivalence :: (Semigroup m) => Semigroup (On m a) where
append (On p) (On q) = On (\a b -> p a b <> q a b)
instance monoidEquivalence :: (Monoid m) => Monoid (On m a) where
mempty = On mempty
type Equivalence = On (Conj Boolean)
type Comparison = On Ordering
Ordering is a Monoid; the instance just doesn't show up on the data type because it's defined downstream, in the Data.Monoid module.
good point, updated my comment
That is pretty neat. Shame it would be a breaking change. I think it's probably worth doing, though.
Probably we can schedule this change for next braking change then (let me know when that comes and will open PR)
@safareli Would you mind submitting a PR that adds this? We'll be doing breaking changes before v0.14.0
is released.
Unfortunately I'm busy this month :(
This doesn't work out when I try to implement the Decide
instance for Comparison
via On
:
instance chooseOn :: Decide (On m) where
choose f (On g) (On h) = On \a b -> case f a of
Left c -> case f b of
Left d -> g c d
Right _ -> LT
Right c -> case f b of
Left _ -> GT
Right d -> h c d
Fails with this error. We could supply mempty
, but then the instance's implementation has been changed. The Equivalence
version supplies false
on the Left-Right and Right-Left branches:
1/1 TypesDoNotUnify] src/Data/Decide.purs:19:15
v
19 Left c -> case f b of
20 Left d -> g c d
21 Right _ -> LT
^
Could not match type
Ordering
with type
m3
while checking that expression LT
has type t2
while applying a function On
of type (t0 -> t0 -> t1) -> On t1 t0
to argument \a ->
\b ->
case (f a) of
(Left c) -> ...
(Right c) -> ...
in value declaration chooseOn
where m3 is a rigid type variable
bound at (line 0, column 0 - line 0, column 0)
t0 is an unknown type
t1 is an unknown type
t2 is an unknown type
Thanks for looking into this @JordanMartinez. After looking at your PR and thinking about this a little bit more, and I’m not quite as keen as I was a year ago for a couple of reasons:
- The specialised behaviour in the Divide instances for Equivalence and Comparison makes it feel like maybe they should be kept separate after all?
- If we are wanting to factor out the basic pattern for what the Comparison and Equivalence instances have in common, I feel like that behaviour might be better to hang on the
Op
data type, since a function of two arguments is really just a function which returns a function. In fact I thinkOp
has the relevant instances already:
newtype Op a b = Op (b -> a)
derive newtype instance semigroupOp :: Semigroup a => Semigroup (Op a b)
derive newtype instance monoidOp :: Monoid a => Monoid (Op a b)
So then I think On m a
is “just” Op (Op m a) a
.
The specialised behaviour in the Divide instances for Equivalence and Comparison makes it feel like maybe they should be kept separate after all?
That was my thinking.
So then I think On m a is “just” Op (Op m a) a.
So, would we still support On
? I'm not sure what the next action here would be.
Since we already have Op which works in a similar way for any monoid, and since Equivalence and Comparison have specialized Divide instances, I’m tempted to leave everything as-is. I’m leaning towards not introducing On, since it’s a special case of Op.
Should we close this then?
Yes, I think so.