Reputation: 794
Consider the following typeclass for defining equality between values of heterogenous types:
class HEq a b where
heq :: a -> b -> Bool
We can define an instance for Int
and Bool
as follows:
instance HEq Int Bool where
heq 1 True = True
heq 0 False = True
heq _ _ = False
But if HEq Int Bool
then we can also say that HEq Bool Int
. Is there a way to capture this relationship without having to define two instances?
A naive approach would be the following:
instance HEq a b => HEq b a where
heq = flip heq
But this requires UndecidableInstances
and needs to be annotated as OVERLAPPABLE
to even satisfy the compiler. And then we enter infinite recursion territory on anything that doesn't have an explicit instance defined since it just flips back and forth indefinitely.
Upvotes: 3
Views: 104
Reputation: 92117
This is a little bit less automated than Daniel Wagner's answer, but may be satisfactory as it doesn't get in the way of your existing default instance.
An obvious first step towards a solution (with some drawbacks) would be to define a newtype wrapper to tell the compiler that you want to use the flipped equality rules:
newtype EqH a = EqH a
instance HEq b a => HEq a (EqH b) where
heq a (EqH b) = heq b a
Now you never need to write your instance HEq Bool Int
, because the client can always call heq True (EqH 1)
and get the newtype version. Great, the implementation is easy, but now the callsites suck: they have to know whether to call heq x y
or heq x (EqH y)
based on which version of HEq
is defined, and even if they could keep track of which to use it's a nuisance to write the EqH
wrapper.
You can make the callsites perfect by doing a little bit more work in the implementation. Delegating to a typeclass instance through a newtype is a very common pattern, and it's what DerivingVia
is for. So if, in addition to the above newtype, you add a deriving instance for each pair of types you give an HEq
instance to, clients can call heq
with the arguments swapped, no problem:
instance HEq Int Bool where
heq 1 True = True
heq 0 False = True
heq _ _ = False
deriving via (EqH Int) instance HEq Bool Int
ghci> heq (1 :: Int) True
True
ghci> heq True (1 :: Int)
True
So in the end it's not free: you do have to write one instance declaration per instance you want to define. But the deriving instance is very easy to write, taking much less work than defining the original instance and not requiring you to rewrite the code of the flip
instance either.
Upvotes: 4
Reputation: 153172
You can't write a blanket HEq a b => HEq b a
instance. But you could add a default implementation to the class declaration.
{-# Language DefaultSignatures #-}
class HEq a b where
heq :: a -> b -> Bool
default heq :: HEq b a => a -> b -> Bool
heq = flip heq
With that in place, you can write your flipped instances with no body.
instance HEq Bool Int
instance HEq Int Bool where
heq = ...
instance HEq () Char
instance HEq Char () where
heq = ...
Upvotes: 5