[Haskell-cafe] Restricted type classes

wren ng thornton wren at freegeek.org
Sat Sep 4 04:27:24 EDT 2010


On 9/4/10 3:50 AM, Ivan Lazar Miljenovic wrote:
> On 4 September 2010 17:40, wren ng thornton<wren at freegeek.org>  wrote:
>> 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.
>
> I think I'd prefer to put the prefix on the kind * versions (though
> does a kind * version of something like Monad even make sense?).

Oh sure. I was just meaning that you should do something systematic like 
have XFoo and YFoo, instead of having Foo and Bar. That way people can 
just remember ({X,Y},{Foo,Fob,Fez}) instead of having to remember 
{(Foo,Bar), (Fob,Baz), (Fez,Quux)}.

I'm a fan of keeping the undecorated names for the current versions, and 
using a prefix for the * versions.

>> 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.
>
> Monoid was probably just going to be re-exported from base, since it's
> already for kind * (and as such works with all types, and doens't need
> any parametricity).

I was just talking general API, not necessarily what you need to write. 
Reusing the standard classes is fine.

> Well, the point was that liftA/liftA2 might make more sense as being
> the methods to be defined for some types rather than<*>, but you
> could define them in terms of each other.

Well, liftA and liftM are just generic implementations of fmap using 
pure/(<*>) and return/(>>=). If you can define fmap directly, then you 
should do so. If you can't, you're always free to use the 
implementations that liftA and liftM use. The liftA function is defined 
explicitly for allowing you can say """instance Functor F where fmap = 
liftA""". There's no reason for liftA to belong to Applicative, because 
it has Functor as a superclass and therefore fmap exists. Since fmap can 
be more efficient than liftA, it is better to use fmap when writing 
code. Note that (<$>) is defined as fmap, and liftA2, liftA3, etc are 
defined in terms of (<$>) i.e. fmap.

For liftM things are a bit hazier because Monad fails to mention Functor 
as an explicit superclass, but it really ought to. Assuming it did, then 
there's no reason for liftM to belong to Monad, because we're assured 
that fmap exists. Though, again, liftM should be defined as a non-class 
function in order to serve as a default implementation of fmap for those 
who need it. This is the same reason why Data.Traversable defines 
fmapDefault and foldMapDefault: not so that they can be used as 
functions, but so that they can be used for giving class instances. 
Perhaps we should be calling them fmapApplictiveDefault, 
fmapMonadDefault, and fmapTraversableDefault instead...

I suppose you could use liftA2 as the basis of Applicative instead of 
(<*>), but that seems a bit perverse to me. The (<*>) operation captures 
exactly what we want to say--- namely the K axiom for modal logics; 
which is equivalent to saying the applicative category has exponentials; 
which is also the name for the whitespace of function application;... . 
Whereas liftA2 seems like a far more round-about way of getting there. 
There may be efficiency reasons for arguing that liftA2 should be 
included in _addition_ to (<*>), but I'm not aware of them.

-- 
Live well,
~wren


More information about the Haskell-Cafe mailing list