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?