[Haskell-cafe] Decoupling type classes (e.g. Applicative)?

Dominique Devriese dominique.devriese at cs.kuleuven.be
Fri Oct 29 09:35:56 EDT 2010

Hi all,

I have a problem with the design of the Applicative type class, and
I'm interested to know people's opinion about this.

Currently, the Functor and Applicative type class are defined like this:

  class  Functor f  where
      fmap        :: (a -> b) -> f a -> f b

  class Functor f => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b

My problem is that in the grammar-combinators library [1], the pure
combinator is too general for me. I would propose a hierarchy like
the following:

  class  Pointed f  where
      pure :: a -> f a

  class  ApplicativeC f where
    (<*>) :: f (a -> b) -> f a -> f b

The original type class Applicative can then be recovered as follows,
and the applicative laws can be specified informally in this class's

  class  (Pointed f, ApplicativeC f, Functor f) => Applicative f where

This would allow me to restrict injected values to stuff I can lift
into Template Haskell later on:

  class  LiftablyPointed f where
      pureL :: (Lift a) -> a -> f a
      pureL' :: a -> Q Exp -> f a

  class  (LiftablyPointed f, ApplicativeC) => LiftablyApplicative f where

This problem currently makes it impossible for me to use the (<*>)
combinator and I have to redefine it under a different name (I
currently use (>>>)). To me the problem seems similar to the well
known example of the inclusion of the fail primitive in the monad
class, where the general opinion seems to be that it was a bad idea to
include fail in the Monad class (see
e.g. the article on the haskell wiki about handling failure [2]).

I've been thinking about the following type class design principles:

* Only include two functions in the same design class if both can be
  implemented in terms of each other.

* Only introduce a dependency from type class A to type class B if all
  functions in type class B can be implemented in terms of the
  functions in type class A or if type class A is empty.

(Disclaimer: I currently do not follow these principles myself ;))

I would like to know people's opinions about this. Are there any
issues with this advice that I don't see? Have other people
encountered similar problems? Any interesting references?


[1]  http://projects.haskell.org/grammar-combinators/
[2]  http://www.haskell.org/haskellwiki/Failure

More information about the Haskell-Cafe mailing list