Type constrain in instance?

Hi, I got an issue when playing haskell's type system, partically parametic class/instance have no way to specify constrains of their parameters. for example, i want to data struct to chain functions and their derivatives so we can have combined function and derivations, as following import qualified Control.Category as Cat data ChainableFunction a b = CF { cfF :: (a->b), cfDeriv :: (a->b) } class Module a b wher4e (*>) :: a -> b -> b instance Cat.Category CF where id :: (Num a) -- GHC dis-allow this id = CF id (const 1) (.) :: (Num a, Num b, Num c, Module a b, Module b c) => CF a b -> CF b c -> CF a c -- GHC disallow this either (.) (CF f f') (CF g g') = CF (g.f) (\a -> f' a *> g' (f a)) However GHC only has kinds for class/instance like (*->*->*) so we are forced to allow all possible types in instance code. I'm not sure if I'm modelling things correctly or is there another way to do the same thing? Cheers, Louis

You need to define the instance with `ChainableFunction' and not `CF' (the latter is value-level, not type level). Once you make that switch, you'll find yourself with an instance that is not compatible with the definition of the `Category' class. Prelude Control.Category> :info Category class Category cat where id :: forall a. cat a a (.) :: forall b c a. cat b c -> cat a b -> cat a c -- Defined in Control.Category We see that `id' and `.' have no class constraints and that there is in fact no where to place a class constraint on `a', `b' or `c'. I think that what you're looking for are "restricted categories" (and restricted monads and functors, as well, perhaps). A cursory search suggests the `data-category' package. -- Jason Dusek

On Fri, Apr 9, 2010 at 11:22 AM, Louis Zhuang
However GHC only has kinds for class/instance like (*->*->*) so we are forced to allow all possible types in instance code. I'm not sure if I'm modelling things correctly or is there another way to do the same thing?
As far as I know, it is indeed not generally possible to constrain unmentioned parameters to a type constructor in an instance declaration. There are workarounds involving modifications to the class definition, but as you want to use a class from the standard libraries, that helps very little. For the most part this is by design; the standard type classes are intended to be fully generic in their parameters. However, it seems that you are creating your own special-purpose data type, so one possible solution presents itself: Constrain the ChainableFunction type to permit construction only with numeric types. Simply placing a Num context on the data declaration fails, however, as this demands a similar constraint on functions using the type--which is exactly what we're trying to achieve, and so is spectacularly useless. One conventional solution is to instead conceal the actual data constructor from client code, instead exposing only constructor/deconstructor functions with appropriate constraints; the downsides to this are that client code cannot use pattern matching on the type, and that internal code must carefully maintain constraints anywhere the type is used. A more aesthetically appealing approach, if you're not averse to language extensions, is GADTs: Place constraints on the CF data constructor, not the type. With CF the sole constructor the constraints will be enforced everywhere, and best of all pattern matching on CF will provide the necessary context--making the constraint visible even inside the instance declaration for the supposedly fully-generic (.)! Alas, it now seems difficult to describe id; it must create a ChainableFunction with any type (as per the Category class), and without pattern matching on a ChainableFunction argument it has no way of getting the constraints. But consider that, for the same reasons, id has no way of actually doing anything with the type parameters with which it must construct a ChainableFunction, and thus shouldn't need to have them at all; further, the semantics of id are quite simple and essentially independent of its parameterized "content". Thus, we can add another constructor to ChainableFunction, that takes no arguments and constructs a value of type (ChainableFunction a a), and extend the definition of (.) to make the behavior of the identity explicit. The result will look something like this: {-# LANGUAGE MultiParamTypeClasses, GADTs #-} import qualified Control.Category as Cat data ChainableFunction a b where CF :: (Num a, Num b) => (a->b) -> (a->b) -> ChainableFunction a b CFId :: ChainableFunction a a instance Cat.Category ChainableFunction where id = CFId CF g g' . CF f f' = CF (g.f) (\a -> f' a *> g' (f a)) CFId . f = f g . CFId = g You've probably noticed that I've been ignoring the Module class. Unfortunately, the solution thus far is insufficient; a Module constraint on the CF constructor does work as expected, providing a context with (Module a b, Module b c), but the result requires an instance for Module a c, which neither available, nor easily obtained. I'm not sure how best to handle that issue; if you find the rest of this useful, hopefully it will have given you enough of a start to build a complete solution. - C.

Casey McCann
{-# LANGUAGE MultiParamTypeClasses, GADTs #-} import qualified Control.Category as Cat
data ChainableFunction a b where CF :: (Num a, Num b) => (a->b) -> (a->b) -> ChainableFunction a b CFId :: ChainableFunction a a
instance Cat.Category ChainableFunction where id = CFId CF g g' . CF f f' = CF (g.f) (\a -> f' a *> g' (f a)) CFId . f = f g . CFId = g
You've probably noticed that I've been ignoring the Module class. Unfortunately, the solution thus far is insufficient; a Module constraint on the CF constructor does work as expected, providing a context with (Module a b, Module b c), but the result requires an instance for Module a c, which neither available, nor easily obtained. I'm not sure how best to handle that issue; if you find the rest of this useful, hopefully it will have given you enough of a start to build a complete solution.
- C.
Thanks for the comment. If we try to use GADT to construct Cat.id, actually (Numa) constraint is redundant because I just want "1" for first derivative of x. However instance (Module a b, Module b c) => Module a c is a must for chain rule... I'm looking at Data.Category suggested by Jason, because it allows subset of Hask object to be applied into parameters
participants (3)
-
Casey McCann
-
Jason Dusek
-
Louis Zhuang