Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constraining a derived variable of a type class or an instance

Tags:

haskell

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.

like image 997
Gabriella Gonzalez Avatar asked Sep 18 '12 21:09

Gabriella Gonzalez


People also ask

What is an instance of a Haskell type class?

An instance of a class is an individual object which belongs to that class. In Haskell, the class system is (roughly speaking) a way to group similar types. (This is the reason we call them "type classes"). An instance of a class is an individual type which belongs to that class.

What is a constraint in C++?

A constraint is a requirement that types used as type arguments must satisfy. For example, a constraint might be that the type argument must implement a certain interface or inherit from a specific class. Constraints are optional; not specifying a constraint on a parameter is equivalent to using a Object constraint.

What is the purpose of an interface constraints on a type parameter?

Interface Type Constraint You can constrain the generic type by interface, thereby allowing only classes that implement that interface or classes that inherit from classes that implement the interface as the type parameter.

What is type parameter in C#?

In a generic type or method definition, a type parameter is a placeholder for a specific type that a client specifies when they create an instance of the generic type.


1 Answers

a rather trivial way to do this is use a GADT to move the proof to the value level

data IsMonad m where
  IsMonad :: Monad m => IsMonad m 

class ProxyC p where
  getProxyMonad :: Monad m => IsMonad (p a' a b' b m)

you will need to explicitly open the dictionary wherever you need it

--help avoid type signatures
monadOf :: IsMonad m -> m a -> IsMonad m
monadOf = const

--later on
case getProxyMonad `monadOf` ... of
  IsMonad -> ...

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.Constraint package

class ProxyC p where
  getProxyMonad :: Monad m => Dict (Monad (p a' a b' b m))

which lets you defined

getProxyMonad' :: ProxyC p => (Monad m) :- (Monad (p a' a b' b m))
getProxyMonad' = Sub getProxyMonad

and then use a fancy infix operator to tell the compiler where to look for the monad instance

 ... \\ getProxyMonad'

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

newtype WrapP p a' a b' b m r = WrapP {unWrapP :: p a' a b' b m r}

instance ProxyC p => Monad (WrapP p) where
  return = case getProxyMonad of
                Dict -> WrapP . return
  (>>=) = case getProxyMonad of
               Dict -> \m f -> WrapP $ (unWrapP m) >>= (unWrapP . f)

instance ProxyC p => ProxyC (WrapP p) where
  ...

I suspect, but obviously have not tested, that this implementation will also be relatively efficient.

like image 168
Philip JF Avatar answered Sep 28 '22 18:09

Philip JF