[Haskell-cafe] Monad transformer responsibilities

Claus Reinke claus.reinke at talk21.com
Fri Jun 5 07:25:52 EDT 2009


> >From what I understand, the current best practices are to build your
> package dependencies like so:
> 
> Parsec    MyMonadT
> MyMonadT_Parsec   -- orphan instances go here
> ProjectPackage
> 
> This does mean splitting up your project into three packages, but
> decouples the orphan instance into its own package where it can do the
> least damage :)

Lets assume the above are modules rather than packages (same 
difference, but fewer indirections in the explanation to follow): if 
ProjectPackage imports MyMonadT_Parsec and is itself meant
to be imported by other modules, then that decoupling breaks down
(unless MyMonadT is a private class, in which case there is only
one provider of instances, who can try to manage the situation).

> At the very least it should go into its own module which can be
> imported only in the places that need it, similar to
> Control.Monad.Instances defining the orphan instance for Monad ((->)
> r).

Orphan instances aren't themselves bad, I think. But since current
technology doesn't allow for import/export control, they always
indicate a problem, hence the warning.  When possible, the problem
should be avoided, by making either the class or the type private
(if neccessary by wrapping a common type in a newtype). That 
doesn't mean that the problem can always be avoided, just that 
there is something that needs attention. Back to that import hierarchy:

> Parsec    MyMonadT
> MyMonadT_Parsec   -- orphan instances go here
> ProjectPackage

If ProjectPackage is meant to be imported, there are at least two 
ways to proceed. Version A is to split the dependent modules, so 
that each of them can be used with or without the import.

 Parsec    MyMonadT
 MyMonadT_Parsec   -- orphan instances go here
 ProjectPackageWith -- imports, and implicitly exports, MyMonadT_Parsec
 ProjectPackageWithout -- no import, no implicit export

So clients can still use ProjectPackageWithout if they get the
instances by another route. This only works for convenience 
instances where the instances are nice to provide for clients,
but not needed in ProjectPackage itself - in essence:

 ProjectPackageWith(module ProjectPackageWithout) where 
 import MyMonadT_Parsec
 import ProjectPackageWithout 

If ProjectPackage actually depends on the existence of those 
orphan instances, plan B is to delay instance resolution, from 
library to clients, so instead of importing the orphan instances

 module ProjectPackage where 
 import MyMonadT_Parsec
 f .. =  .. orphan instances are available, use them ..

(which leads to the dreaded implicit export), you'd just assert 
their existence:

 module ProjectPackage where 
 f :: .. Orphan x => .. ; f .. = .. use orphan instances ..

so the client module would have to import both ProjectPackage 
and MyMonadT_Parsec if it wants to call 'f', or find another way
to provide those instance. Of course, the same concerns apply to 
the client modules, so you end up delaying all instance resolution 
until the last possible moment (at which point all the orphans need to be imported).

If there is a main module somewhere (something that isn't itself
imported), that is the place where importing the orphan instances
won't cause any trouble (other than that all the users of such
instances better have compatible ideas about what kind of instance
they want, because they all get the same ones).

If there is no main module (you're writing a library meant to
be imported), you better delay the import of any orphans or
provide both libraryWith and libraryWithout. It isn't pretty.

Claus




More information about the Haskell-Cafe mailing list