instance visibility (was: Re: The base library and GHC 6.10)

Claus Reinke claus.reinke at talk21.com
Wed Sep 24 12:30:27 EDT 2008


> (sorry for the delay in replying to this...)

No problem, though you seem to be restating your opinion 
intead of addressing my concrete points? For those who, like
me, have lost the thread in the meantime, here is a link to the 
message you reply to:

http://www.haskell.org/pipermail/libraries/2008-September/010623.html
 
>>> .., the only sensible way to think about instances is as global properties.
> ..it has nothing to do with bugs or misfeatures in GHC, it's a 
> fact of Haskell 98.

I thought my example demonstrated quite clearly that instances 
are *not* global in Haskell.

>> A type class specifies a relation between types. Both the types
>> and the class are named, and if instances are placed in separate
>> modules, the modules are named as well. The combination of
>> module, class and type names gives quite a bit of control over instance 
>> import/export, even if it is terribly cumbersome and
>> limited (and easily defeated by just one library importing all
>> instances "for convenience"). Neither the relation (class), nor
>> its domain (types), nor its extent (instances) are "global".
> 
> The point is that instances are unconditionally re-exported, 

Yes, and I'm not disputing that point. What I am disputing are
its consequences/interpretation. As I said, instances accumulate 
upwards along the import hierarchy. But they do not propagate 
downwards, so they do not have global scope, and one can exert 
some control over all of type relation (class), domain (types), and 
extent (instances).

What one cannot do (in Haskell 98) is to have two instances of
the same class, for the same types, in the same import hierarchy.

> If we consider instances to be part of the API of a module, then the 
> API of a module is changed simply by changing what is imported.  

That is true, whether we like it or not. The language does not give 
the programmer a way to control this part of the API by limiting 
re-export of locally visible instances. The programmers may not like
it, but those instances are visible to importers even if they distribute
warning labels stating "these instances are not part of our intended API".

> This is even worse at the level of packages.  We can hide modules that 
> are used internally to a package's implementation, but we can't hide the 
> fact that a package used some non-standard instances internally, and 
> furthermore we can't change this aspect of its implementation without 
> changing the API.

How do packages make a difference here? As long as I don't import
base:Control.Monad.Error, the base:Control.Monad.Instances instances
of Functor, say, are not visible in base:Control.Monad.
 
> So thinking of instances as part of the API of a module is wrong, 
> because it leads to the aforementioned abstraction failures.  The only 
> sensible way to think of instances is as global properties - one 
> instance per class/type pair.

I'm afraid that is the wrong way round: the abstracton failures occur
because of lack of control over the API - trying to wish away the
offending instances ("don't think of them as part of the API") won't 
help. And trying to think of instances as global fails to account for
the limited control we do have, by arranging code inside the import
hierarchy, as in the example I gave. So this interpretation is unhelpful,
demonstrably wrong, and prevents more detailed analysis.

There may be real theory reasons why the extent of type relations
has to increase monotonically as one walks up the import hierarchy,
and such reasons might invalidate the proposal I made about providing
more control over instance re-export. I'd certainly be interested in
hearing about such real arguments (the fact that moving code downwards
in the import hierarchy, away from the branch that provides the instances,
has the same effect, suggests that such reasons either do not exist or
already point to issues with the current language definition).

But simply jumping from "we don't have full control" to "we don't have
any control, and we never will" is unhelpful. We need to understand these 
issues better, so that we can discuss whether the language can be improved, 
and so that we can see what needs to change so that Ghc and Haddock 
start supporting the existing language. Clarity of language precedes better
understanding, I hope.

> Orphan instances are usually wrong unless the orphans are also exported 
> via the standard API for either the class or the type.  That is, orphans 
> are ok in the implementation of a package, but not in the exposed API, 
> because that makes it possible for a client to import both the class and 
> type without getting the instance, which is what we have to avoid.

Sadly, that has all been discussed to death already, and again, it is a 
matter of being precise. "Orphan" instances are not wrong per se - 
they encode and name the extent of type relations via modules, but one 
needs to think carefully about their intended use and whether that use is 
really supported by the language or just an illusion. Of the top of my 
head, I can think of two uses:

(a) having two instances of the same class for the same types in the 
    same program only works by "virtue" of #2356, so should be
    avoided unless and until the positive aspects of #2356 are moved
    from accident to design decision

(b) giving clients control over which instances they want to use (eg,
    use set A or set B, or neither) should work, and mostly does,
    but may run into ghc #2182 and haddock #54. Also, it is 
    advisable only for client applications, not for client libraries, as 
    long as their users might run into unresolved aspect of (a).

My preference would be to see ghc #2182, #2356 (for Haskell 98
mode) and haddock #54 fixed. #2356 (for Ghc mode) is documented
behaviour, I believe, inherited from Ghc's handling of overlapping
instances, but there is no LANGUAGE extension specifying this
behaviour, so it isn't portable. Next, I'd like to see whether more
control over instance re-export is permissible in theory and -if yes-
would like to see it implemented and standardised.

Currently, I see no reason why we couldn't support (a) and (b).
Claus




More information about the Libraries mailing list