instance visibility

Simon Marlow marlowsd at gmail.com
Fri Sep 26 16:51:07 EDT 2008


Claus Reinke wrote:
>>> We have a test in GHC's test suite called instance-leak, which tests that none of the Haskell 98 
>>> libraries exports the Functor instance for (->).  I broke this test recently by accident.  How 
>>> did I break it?  By using Data.Map internally in System.Process.  The Functor instance for (->) 
>>> is exported from Control.Monad.Instances, makes its way into Data.Map, then into System.Process 
>>> and thereby into System, which is a Haskell 98 module.
>>>
>>> In your opinion, who or what is at fault for this test failure?
>> You, of course:-p
>> But seriously: I can't see easily how the instance even gets into Data.Map, but since Data.Map 
>> doesn't seem to be using either
>> Functor or fmap (other than defining an instance itself), it has no business importing (and thus 
>> re-exporting) the instance. If it is
>> actually using any of the other instances (is it?), then perhaps
>> Control.Monad.Instances needs to be split up.
> 
> It seems that the ultimate offender is Control.Applicative, which
> imports Control.Monad.Instances() for no other reason than to
> infect its importers. That then propagates all over the place.

Good!  I completely disagree.  Control.Monad.Instances is at fault for 
exposing an orphan instance in the first place.  Every other answer to 
the question leads to problems, I claim.

Why is it wrong to say that Control.Applicative, or Data.Map, or my use 
of Data.Map in System.Process, are at fault?  Because all of these 
modules just happen to be importing something that exports an orphan 
instance, and they can't be held responsible for re-exposing the orphan 
instance because they have no control over that.

You will argue that they have control over what they import, and can 
thereby control whether they export the instance.  This is certainly 
true, but it ignores the need for module abstraction: the need to be 
able to change the implementation of a module without changing its API. 
  We must have the property that the imports of a module do not affect 
its API - and the only way to have this property is to avoid orphan 
instances in library APIs.

(hmm, I finally feel like I've explained this clearly.  I hope it comes 
across that way.)

There are basically only two sensible choices for the Functor instance 
for (->):

  (a) don't define one at all
  (b) define one in Control.Monad, and give up on Haskell 98 compliance

The current situation, namely

  (c) define it as an orphan, and give up on module abstraction

is not a sensible choice.

Incedentally, Iavor Diatchki pointed out to me today a nice way to 
provide "optional" instances for things in libraries.  In your library 
if you have an abstract type T and want to suggest, but not mandate, a 
Functor instance, then you could export a function

   fmapT :: (a->b) -> T a -> T b

so that a client can easily say

  instance Functor T where fmap = fmapT

if they want.  (but not in a library!  A library would have to wrap T in 
a newtype to avoid an orphan instance.)

> I assume the rationale is "convenience" again? Saving one import
> in importers at the price of limiting the modules that can afford to
> import Control.Applicative in the first place?
> 
> Since libraries@ is the maintainer for Control.Applicative, may
> I suggest to add Control.Applicative.Alt, differing from the original
> only in omitting the import of Control.Monad.Instances? Then all
> importers of Control.Applicative should be checked for whether
> import of Control.Applicative.Alt is sufficient, and be switched
> to that if possible.

That justs moves the problem around, and doesn't fix it.

Cheers,
	Simon



More information about the Libraries mailing list