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

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?
participants (1)
-
C.Reinke