Clinton
Clinton

Reputation: 23135

Creating an instance without requiring a newtype dummy

Lets say I have the following:

class C a where
  f :: a -> Blah  

f_impl_for_d :: D a => a -> Blah
f_impl_for_d = _ 

newtype Alice f a = Alice (f a)

And maybe some existing instances like so:

instance C a => C [a] where ...
instance C a => C (Identity a) where ... 
instance C a => C (Maybe a) where ...

What I want is an instance like this:

instance (D a, C a => C (f a)) => C (Alice f a)

As in, wherever D a is valid, and there's an instance C (f a) with the constraint C a, then I should be able to derive C (Alice f a)

For example, if I have D a, then I should have C (Alice [] a), and C (Alice Maybe a) etc.

Now I could do this:

instance D a => C a where
  f = f_impl_for_d

instance C (f a) => C (Alice f a) where
  f (Alice x) = f x

But that top instance is rediculously overlapping.

Any other way? The only way I've worked out is to make a newtype like so:

newtype T x = T x

instance D x => C (T x) where
  f (T x) = f_impl_for_d x

instance (Functor f, C (f (T x))) => C (Alice f x) where 
  f (Alice x) = f (T <$> x)

But that seems somewhat convoluted. Any nicer way? I tried mucking around with quantified constraints but I didn't get far.

Upvotes: 1

Views: 91

Answers (1)

Li-yao Xia
Li-yao Xia

Reputation: 33519

C a => C (f a) isn't quite what you need. You might think of it as a function C a -> C (f a) but its meaning is technically a bit more refined than that: the C a constraint can only be solved using instances; that function cannot take arbitrary arguments.

You can define a (kind of) subclass which provides such a function instead:

class (forall a. C a => C (f a)) => C1 f where
  f1 :: (a -> Blah) -> (f a -> Blah)

Which then must be implemented explicitly, for example Maybe, using your current implementation of C a => C (Maybe a), and that C instance can be replaced with a default f1 f.

instance C1 Maybe where ...
instance C a => C (Maybe a) where
  f = f1 f

Then you can have

instance (D a, C1 f) => C (Alice f a) where
  f (Alice x) = f1 f_impl_for_d x

This is similar to the Eq1, Ord1, Show1 classes in base. The initial intent was to encode a quantified constraint (forall a. Eq a => Eq (f a)) (before they were a thing), but technically these classes are slightly more expressive than that because the constraint Eq a (which identifies a globally unique value) is replaced with a type (which may have many values).

Upvotes: 5

Related Questions