I’m writing a type class for my pipes library to define an abstract interface to Proxy-like types. The type class looks something like:
class ProxyC p where
idT :: (Monad m) => b' -> p a' a b' b m r
(<-<) :: (Monad m)
=> (c' -> p b' b c' c m r)
-> (b' -> p a' a b' b m r)
-> (c' -> p a' a c' c m r)
... -- other methods
I’m also writing extensions for the Proxy type that are of the form:
instance (ProxyC p) => ProxyC (SomeExtension p) where ....
… and I’d like these instances to be able to impose an additional constraint that if m is a Monad then p a' a b' b m is a Monad for all a', a, b', and b.
However, I have no clue how to cleanly encode that as a constraint either for the ProxyC class or for the instances. The only solution I currently know of is to do something like encoding it in the method signatures of the class:
(<-<) :: (Monad m, Monad (p b' b c' c m), Monad (p a' a b' b m))
=> (c' -> p b' b c' c m r)
-> (b' -> p a' a b' b m r)
-> (c' -> p a' a c' c m r)
… but I was hoping there would be a simpler and more elegant solution.
Edit: And not even that last solution works, since the compiler does not deduce that (Monad (SomeExtension p a' a b' b m)) implies (Monad (p a' a b' b m)) for a specific choice of variables, even when given the following instance:
instance (Monad (p a b m)) => Monad (SomeExtension p a b m) where ...
Edit #2: The next solution I’m considering is just duplicating the methods for the Monad class within the ProxyC class:
class ProxyC p where
return' :: (Monad m) => r -> p a' a b' b m r
(!>=) :: (Monad m) => ...
… and then instantiating them with each ProxyC instance. This seems okay for my purposes since the Monad methods only need to be used internally for extension writing and the original type still has a proper Monad instance for the downstream user. All this does is just expose the Monad methods to the instance writer.
a rather trivial way to do this is use a GADT to move the proof to the value level
you will need to explicitly open the dictionary wherever you need it
the tactic of using GADTs to pass proofs of propositions is really very general. If you are okay using constraint kinds, and not just GADTs, you can instead use Edward Kmett’s
Data.Constraintpackagewhich lets you defined
and then use a fancy infix operator to tell the compiler where to look for the monad instance
in fact, the
:-entailment type forms a category (where the objects are constraints), and this category has lots of nice structure, which is to say it is pretty nice to do proofs with.p.s. none of these snippets are tested.
edit:
you could also combine the value level proofs with a newtype wrapper and not need to open GADTs all over the place
I suspect, but obviously have not tested, that this implementation will also be relatively efficient.