6

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?

Clinton
  • 22,361
  • 15
  • 67
  • 163
  • 1
    A non-answer: you might use `Identity` in the first case and a suitable newtype in the second one (I can't think of any standard one right now). That is a crude workaround, but it might prove less painful than the advanced trickery this seems to call for. By the way, the "I don't want to just make it `f :: (a, b) -> (c, d)` as I want to maintain the invariant..." sounds a lot like the issue of *lens* lenses being `Lens s t a b` [even though the four parameters are mutually dependent](http://comonad.com/reader/2012/mirrored-lenses) (see the "Why is it a Lens Family?" section of the post). – duplode Sep 23 '15 at 07:24
  • I think this is basically the same question as http://stackoverflow.com/questions/4922560/why-doesnt-typesynonyminstances-allow-partially-applied-type-synonyms-to-be-use – Reid Barton Sep 23 '15 at 16:17

1 Answers1

5

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 newtypes 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 newtypes, this does not help much. If instead you want a simple way to remove newtypes wrappers from instance methods, it can help.

chi
  • 111,837
  • 3
  • 133
  • 218