Instances in libraries (e.g., Control.Monad)

C.Reinke C.Reinke@ukc.ac.uk
Fri, 15 Nov 2002 15:16:49 +0000


Hi,

[yet another haskell list to subscribe to.. and I'll start with
 a rant, sorry - I've quickly scanned the archive, but this 
 doesn't seem to have come up before, has it?]

Problem:

As noted elsewhere, we've been running into problems with Haskell's
"fine" handling of instances while trying to combine some largish
Haskell projects. As long as the language isn't changed, this
problem needs to be accounted for in library designs.

Concrete examples: Control.Monad.Error, Control.Monad.Reader

  These modules define lots of instances for (Either a) and ((->) a).
  Why?  Obviously, they want to use them, but that's no excuse.

  Both Either and (->) are very popular type constructors, and
  currently, every project that wants to use these instances defines
  them somewhere.  Result: as soon as you put two of these projects
  together, you get a clash!

In current Haskell, defining instances of public classes for public
types/constructors has side-effects running through the whole of any
program using such a library. Quite apart from the facts that the
module names typically gives no indication of these side-effects,
and that the modules could have been implemented without them (using
standard types/constructors was just more convenient), there's no
way to get two such modules to work together in a larger project
without changing them!

Solution: 

I'd suggest a two-way approach:

1) There are some commonly expected, but unfortunately not predefined 
   instances of public classes for public types, such as Monad and
   Functor for (Either a) or for ((->)a)

  These should go into separate modules, to be imported into every
  project depending on them. Result: no conflict, and each of the
  projects works, both on its own and in combination with other
  such projects.

  It is important that the instances can be imported selectively,
  without fear of getting other, unwanted, stuff in the process.

ShowFunctions.hs would have been an example of this (where did
that go, btw?).

In Control.Monad.Error, the case is actually worse: not only does
it have the unexpected side-effect of stealing some "standard"
instances, it defines some of them in non-standard ways:

  instance (Error e) => Monad (Either e) where
    return        = Right
    Left  l >>= _ = Left l
    Right r >>= k = k r
    fail msg      = Left (strMsg msg)

Here, Error and strMsg are local to the Error module, and the design
prescribes that fail *must* return Left of something, rendering
it impossible to use this definition to get any other behaviour.

2) If a module needs instances of public classes for public
   types/constructors, but in non-standard ways, it should
   copy&rename either the class or the type/constructor. This way,
   the standard ADT services of the module mechanism prevent conflicts
   (the instances are still flowing around, but unseen..).

  Given that some syntactic sugar and other functionality depends on
  classes with fixed names, such as Monad, most modules will create
  their own variant of the type, not of the class.

For instance, Control.Monad.Error would probably need to define its
own variant of Either. That could be a simple newtype wrapper.

I haven't checked the libraries systematically. This is just one
example we ran into in practice (meaning that these libraries are
actually used, making them vulnerable to complaints;-).

Hope this makes sense?

Claus

PS. perhaps this suggestion got lost in the other thread, but 
    could Haddock please show the source of any instances in the
    generated documentation?