[Haskell-cafe] Restricted type classes

wren ng thornton wren at freegeek.org
Sat Sep 4 03:40:49 EDT 2010


On 9/3/10 12:16 AM, Ivan Lazar Miljenovic wrote:
> 1) How should I name the kind * versions?  For example, the kind *
> version of Functor is currently called Mappable with a class method of
> rigidMap.  What should I call the kind * version of Foldable and its
> corresponding methods?  Is there a valid system I can use for these?

I think it'd be good to be consistent, whatever you do. For (*->*->*) 
monads I've seen them called GMonads (for "generalized") and indexed 
monads. For the (*->*) monads, I've seen RMonads and parameterized 
monads. Here are some links for those who may be interested; they have 
more links to independent invention by Oleg Kiselyov and also by Bob 
Atkey, as well as a Coq implementation and a mention of Agda. (For my 
part, I've since moved on to playing with monads between different 
categories, which isn't really germane here.)

     http://winterkoninkje.dreamwidth.org/65416.html
     http://winterkoninkje.dreamwidth.org/65585.html

So, in the interest of generality, perhaps you should just pick a letter 
or a short prefix and use that for each of the classes. In my blog posts 
I called them 0-monads, 1-monads, and 2-monads; following Ganesh 
Sittampalam and Matthieu Sozeau, they'd be monads, r-monads, and g-monads.

> 2) How far should I go?  Should I restrict myself to the
> "data-oriented" classes such as Functor, Traversable, etc. or should I
> try to make restricted versions of Applicative and Monad?  Assuming I
> should:

I'd say you should do as much as seems reasonable. I tend to take things 
as far as I can, but that'd end up doing a lot of the same work as 
category-extras. For a general collections library, I think it'd make 
sense to try to keep things simpler than that if possible. The simpler 
it is, the better the uptake will be, so long as it's still complex 
enough to capture what it needs to.

I'd say you should include: Functor, Foldable, Traversable, Pointed, 
Applicative, Monad, and Monoid (both additive and multiplicative in 
separate classes, as in the monoids package). Those eight make for a 
really comprehensive toolkit that does most of the things people 
frequently need. Of course, not every collection will have instances for 
all of them.

(N.B., following the naming convention in those blog posts, 2-monoids 
are exactly the same as categories.)

> 2b) Is it OK to promote functions that use a class to being class
> methods?  When I was discussing this in #haskell several people
> mentioned that defining something like liftA2 for the Set instance of
> (restricted) Applicative would make more sense than the default<*>
> (since (a ->  b) isnt' an instance of Ord).

That depends. In general, it's better to have smaller classes because it 
reduces the burden on programmers writing instances and it allows for 
smaller dictionaries at runtime. In general, I'd say that functions 
should be included into the class when they often permit specialized 
definitions which are more efficient than the generic one. And of 
course, they should be included when they serve as the basis of the 
class (e.g., both (==) and (/=) should be included in Eq since they both 
serve as a basis for equality, with no one being obviously easier to 
implement than the other). If there's little chance of a performance 
gain, then why would you want it in the class at all?

For example, many types can implement fmap/(<$>) more efficiently than 
by using the liftM/liftA implementations or the default implementation 
from Foldable. (Of course, fmap is also a basis for the class.)

For applicatives, the (<*) and (*>) operators should be included in the 
class too. While they seem trivial, some parser combinator libraries can 
get major performance improvements by using non-generic definitions. 
There was a discussion about this a while back, I forget if it was here 
or on the libraries list. And I forget if (<**>) also had efficient 
implementations...

For another example of the principle, while compare is a sufficient 
basis for defining Ord, we include (<), (<=), (>), (>=) because they can 
often be implemented more efficiently than by using compare.


> 2c) Should I keep the classes as-is, or should I explicitly put in the
> constraints mentioned in the Typeclassopedia (e.g. make Applicative an
> explicit superclass of Monad, and define return = pure for
> compatability reasons)?  If so, should I bring over Pointed, etc. from
> category-extras to round out the set or just stick with classes that
> are already in base?

If you're defining a new hierarchy, I'd say you should do it correctly, i.e.

     class                Functor     where fmap
     class Functor     => Pointed     where unit -- or point
     class Pointed     => Applicative where (<*>) ; (<*) ; (*>)
     class Applicative => Monad       where (>>=) ; join

There's no benefit to preserving the ill designs of the past.

> 3) Am I wasting my time with this?

Not at all. Many people want a good containers API, and many people want 
a cleaned up version of the categorical classes which isn't quite as 
involved as category-extras. Go for it!

-- 
Live well,
~wren


More information about the Haskell-Cafe mailing list