
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.