Clinton
Clinton

Reputation: 23135

Generalised newtype deriving on class functions with Functors

I'm developing a class representing key/value mappings, and I've got a function which is basically like alterF:

class C t where
  ...
  alterF :: Functor f => 
    (Maybe (Value t) -> f (Maybe (Value t))) -> Key t -> t -> f t

Unfortunately, this breaks GeneralisedNewtypeDeriving. In some cases, this is reasonable, as GeneralisedNewtypeDeriving from what I understand essentially uses Coercible and the function coerce. Coercible represents types which are representationally equal, i.e. they have the same representation at run time, so we can convert between them for free. For example, given:

newtype T a = T a

we have:

Coercible a (T a)
Coercible (T a) a

but we don't have (in general):

Coercible (f a) (f (T a))
Coercible (f (T a)) (f a)

For example, GADTs violate this representational equality. But there are lots of values of f that do work. For example:

Coercible (Maybe a) (Maybe (T a))
Coercible (Maybe (T a)) (Maybe a)
Coercible [a] [T a]
Coercible [T a] [a]
Coercible (Identity a) (Identity (T a))
Coercible (Identity (T a)) (Identity a)

It also occurs to me that this instance could be written:

Functor f => Coercible (f a) (f (T a))
Functor f => Coercible (f (T a)) (f a)

Just using fmap. Unlike the usual coerce, this wouldn't be free at runtime, but it will work.

So I've got a class with 10 functions, 9 of which work fine with GeneralisedNewtypeDeriving. There's just this final one which doesn't, which could be resolved mechanically using fmap. Do I have to write custom wrapping/unwrapping implementations for all my class functions, or is there a way to either require me to write the implementation for just the problem function or alternatively coax GHC into using fmap as part of it's GeneralisedNewtypeDeriving?

Upvotes: 6

Views: 231

Answers (1)

luqui
luqui

Reputation: 60463

If f is a Functor, you can make a "representational wrapper" for it

data Rep f a where
    Rep :: (b -> a) -> f b -> Rep f a

which is isomorphic to f except that it is representational in a, by what is essentially existential quantification over any nominal variance f might have. I think this construction happens to have some fancy category theory name but I don't remember what it is. To get the f a back out of a Rep f a, you need to use f's Functorhood.

You can use this wrapper in your method, ensuring that your class varies representationally.

alterFRep :: (Functor f) 
          => (Maybe (Value t) -> Rep f (Maybe (Value t))) -> Key t -> t -> Rep f t

And then make the real "method" just a regular function in terms of it by using the isomorphism with Rep f. You can also make a convenience method for instance authors:

toAlterFRep :: 
    (forall f t. (Functor f) => (Maybe (Value t) -> f (Maybe (Value t))) -> Key t -> t -> f t)
 -> (forall f t. (Functor f) => (Maybe (Value t) -> Rep f (Maybe (Value t))) -> Key t -> t -> Rep f t)

so they don't have to worry about what the heck Rep is, they just implement alterF normally and use toAlterFRep on it.

Upvotes: 6

Related Questions