Lets say I've got the class:
class C a b t where
f :: (a, b) -> (t a, t b)
Now with this definition, I can define instances for say:
(a,b) -> (Maybe a, Maybe b)
(a,b) -> ([a], [b])
But not for (as far as I understand):
(a,b) -> (a,b)
(a,b) -> ((a, a), (b, b))
I could instead change my class definition like so:
type family T a b x
class C a b where
f :: (a, b) -> (T a b a, T a b b)
Which would allow me to do the above, but then I'd only be able to declare one f
for each a
and b
.
Basically I want to be able to pass a type family as t
in the original definition, which is implicitly resolved by the type checker if known. I don't want to just make it f :: (a, b) -> (c, d)
as I want to maintain the invariant that both a
and b
have the same thing done to them, so swap . f
is the same type as f . swap
. I'm thinking I might need injective type families (from GHC 8.0) but I'm not sure. But maybe there's another way?
This may or may not answer your question depending on what you actually need to do.
A standard solution is to use a newtype to define new instances. E.g.
newtype Pair a = Pair (a, a)
instance C a b Pair where
f = ...
However, this will cause f
to have type
f :: (a, b) -> (Pair a, Pair b)
instead of the wanted
f :: (a, b) -> ((a, a), (b, b))
The latter can be recovered in a boring way:
f' :: (a, b) -> ((a, a), (b, b))
f' p = case f p of (Pair x, Pair y) -> (x, y)
Now, writing "adapter" functions such as f'
feels redundant: after all we are simply removing a newtype
wrapper, which does not change the runtime representation. Worse, this could be inefficient: consider transforming [Pair a]
into [(a, a)]
. To do that, we would need to map over the whole list, so we pay O(n) do do essentially nothing.
An efficient alternative can be constructed using safe coercions:
import Data.Coerce
f'' :: forall a b. (a, b) -> ((a, a), (b, b))
f'' = coerce (f :: (a, b) -> (Pair a, Pair b))
This allows the use of newtype
s to drive instance selection, and yet remove them when they get in the way.
Now, if your aim really is to define new instances without newtype
s, this does not help much. If instead you want a simple way to remove newtype
s wrappers from instance methods, it can help.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With