Avoiding the hazards of orphan instances without dependency problems

Carlos Camarao carlos.camarao at gmail.com
Thu Oct 23 01:40:12 UTC 2014


+1.

I have followed the road of trying to enable instances to be imported
and exported, without success: a paper that discusses the subject and
argues in favour of this support is available at:
    http://www.dcc.ufmg.br/~camarao/controlling-the-scope-of-instances-in-Haskell-sblp2011.pdf
A previous version was rejected by the 2011 Haskell Symposium program
committee. Referee reports are attached, since perhaps they can be
useful to the discussion.

Carlos

---------- Forwarded message ----------
From: Jan Stolarek <jan.stolarek at p.lodz.pl>
Date: Wed, Oct 22, 2014 at 2:56 PM
Subject: Re: Avoiding the hazards of orphan instances without
dependency problems
To: ghc-devs at haskell.org
Cc: RodLogic <dev at rodlogic.net>, David Feuer <david.feuer at gmail.com>


It seems that my previous mail went unnoticed. Perhaps because I
didn't provide enough
justification for my solution. I'll try to make up for that now.

First of all let's remind ourselves why orphan instances are a
problem. Let's say package A
defines some data types and package B defines some type classes. Now,
package C might make data
types from A instances of type classes from B. Someone who imports C
will have these instances in
scope. But since C defines neither the data types nor the type classes
it might be surprising for
the user of C that C makes A data types instances of B type classes.
So we issue a warning that
this is potentially dangerous. Of course person implementing C might
suppress these warnings so
the user of C can end up with unexpected instances without knowing anything.

I feel that devising some sort of pragmas to define which orphan
instances are allowed does not
address the heart of the problem. And the heart of the problem is that
we can't control importing
and exporting of instances. Pragmas are just a workaround, not a real
solution. It would be much
better if we could just write this (warning, half-baked idea ahead):

  module BazModule ( instance Bar Foo ) where

  import FooModule (Foo (...)) -- import Foo data type from FooModule
  import BarModule (class Bar) -- import class Bar from BazModule

  instance Bar Foo ...

And then someone importing BazModule can decide to import the instance:

 module User where
 import FooModule (Foo(..))
 import BarModule (class Bar)
 import BazModule (instance Bar Foo)

Of course requiring that classes and instances are exported and
imported just like everything else
would be a backawrds incompatible change and would therefore require
effort similar to AMP
proposal, ie. first release GHC version that warns about upcoming
change and only enforce the
change some time later.

Janek

Dnia wtorek, 21 października 2014, RodLogic napisał:
> One other benefit of multiple files to use a single module name is that it
> would be easy to separate testing code from real code even when testing
> internal/non-exported functions.
>
> On Tue, Oct 21, 2014 at 1:22 PM, John Lato <jwlato at gmail.com> wrote:
> > Perhaps you misunderstood my proposal if you think it would prevent
> > anyone else from defining instances of those classes?  Part of the
> > proposal was also adding support to the compiler to allow for a multiple
> > files to use a single module name.  That may be a larger technical
> > challenge, but I think it's achievable.
> >
> > I think one key difference is that my proposal puts the onus on class
> > implementors, and David's puts the onus on datatype implementors, so they
> > certainly are complementary and could co-exist.
> >
> > On Tue, Oct 21, 2014 at 9:11 AM, David Feuer <david.feuer at gmail.com>
> >
> > wrote:
> >> As I said before, it still doesn't solve the problem I'm trying to
> >> solve. Look at a package like criterion, for example. criterion depends
> >> on aeson. Why? Because statistics depends on it. Why? Because statistics
> >> wants a couple types it defines to be instances of classes defined in
> >> aeson. John Lato's proposal would require the pragma to appear in the
> >> relevant aeson module, and would prevent *anyone* else from defining
> >> instances of those classes. With my proposal, statistics would be able
> >> to declare
> >>
> >> {-# InstanceIn Statistics.AesonInstances AesonModule.AesonClass
> >> StatisticsType #-}
> >>
> >> Then it would split the Statistics.AesonInstances module off into a
> >> statistics-aeson package and accomplish its objective without stepping
> >> on anyone else. We'd get a lot more (mostly tiny) packages, but in
> >> exchange the dependencies would get much thinner.
> >> On Oct 21, 2014 11:52 AM, "Stephen Paul Weber"
> >> <singpolyma at singpolyma.net>
> >>
> >> wrote:
> >>> Somebody claiming to be John Lato wrote:
> >>>> Thinking about this, I came to a slightly different scheme.  What if
> >>>> we instead add a pragma:
> >>>>
> >>>> {-# OrphanModule ClassName ModuleName #-}
> >>>
> >>> I really like this.  It solve all the real orphan instance cases I've
> >>> had in my libraries.
> >>>
> >>> --
> >>> Stephen Paul Weber, @singpolyma
> >>> See <http://singpolyma.net> for how I prefer to be contacted
> >>> edition right joseph
> >
> > _______________________________________________
> > ghc-devs mailing list
> > ghc-devs at haskell.org
> > http://www.haskell.org/mailman/listinfo/ghc-devs


_______________________________________________
ghc-devs mailing list
ghc-devs at haskell.org
http://www.haskell.org/mailman/listinfo/ghc-devs
-------------- next part --------------
----------------------- REVIEW 1 ---------------------
PAPER: 27
TITLE: Controlling the scope of instances in Haskell
AUTHORS: Marco Silva and Carlos Camarão

OVERALL RATING: -1 (weak reject)
REVIEWER'S CONFIDENCE: 2 (medium)

Summary:

This paper presents a design proposal that would allow Haskell
programs to have more than one instance of a type class for a given
type within a whole program.  As usual, there can be only a single
instance in scope at any particular point, but which instance is
available can vary from module to module.  The paper proposes to
extend the module import/export syntax to allow instances to be
selectively imported or exported; it also proposes that typeclass
instances can be named (to permit shorter interface definitions).

Review:

This paper does a good job of explaining the limitations of the
current restrictions on Haskell's type classes.  However, as the paper
itself notes, there are potential pitfalls with the proposed solution.
In particular:

- This approach doesn't provide good encapsulation: overloading
  resolution is affected by the scope of instances, so the meaning of
  invariants of encapsulated data structures can be affected.
  (e.g. the Ord constraints on a Set might be used inconsistently in
  different parts of the program).

- Type annotations change the behavior of the program by picking
  particular instances at the point of the annotation.  (But only
  between modules, not within a module.)

This paper points out these problems, but dismisses them as not too
important relative to the extra expressiveness obtained by allowing
multiple instances.

- There are other approaches to type classes that address the problems
  above in a more convincing way.  For example, the paper "Modular
  Type Classes" by Dryer, Harper, and Chakravarty.  This paper doesn't
  mention this related work at all.

- This paper is rather narrowly aimed at Haskell implementors, though
  the proposed design would affect users of Haskell too.  I didn't
  find the discussion of how the proposed language changes could be
  incrementally deployed particularly useful; that kind of planning 
  doesn't seem like it belongs in a conference paper.


Overall I give this paper a 'weak reject'.

----------------------- REVIEW 2 ---------------------
PAPER: 27
TITLE: Controlling the scope of instances in Haskell
AUTHORS: Marco Silva and Carlos Camarão

OVERALL RATING: -2 (reject)
REVIEWER'S CONFIDENCE: 3 (high)

Technical summary:
------------------
The paper argues for a modification of Haskell module import/export
mechanism which allows the programmer to control the scope of
instances and hence (i) give more flexibility to the programmer (such
as the ability to redefine class instances locally) and (ii) avoid
some spurious type checker errors (related to orphan instances).
Concrete examples are presented as well as an intermediate solution
which does not break backwards compatibility with Haskell 2010. There
are no technical results in this paper, but suggestions for
modifications to the Haskell standards.

Opinion:
--------

This paper is an interesting, natural, and convincingly presented
story for controlling the scope of Haskell type class instances. I
believe that a solution along the lines of this proposal is a
plausible one. But, I am pretty sure that more things need to be
investigated so I don't know if this paper is the end of the
story. (For instance the problem outlined in the first paragraph in
page 5 is one that is hard to tackle.) There are also quite a few
issues related to importing/exporting instances that are not
discussed:

  * How exactly should you name instances? Just with their head? Their
    head and context?  What about through type synonyms in the head?

  * One reason that Haskell made everything visible has been to avoid
    incoherence and situations like the impredictability described in
    page 5, but the coherence problem is not mentioned at all in the
    paper.

  * In fact if one wants to talk about exporting/hiding also type
    family instances later on, one has to think not only about
    coherence but also about type soundness.

In addition there is no heavy technical content in the paper. In all,
I think this paper is an extremely useful starting point for a
discussion but I can't quite see it in a published proceedings.

Other points:
-------------

 - pg1, col1, first para: use spaces before placing a citation

 - As another instance of things to think about, maybe it would be a
   good idea to think of the use of wildcards as well to export all or
   none classes from a module

 - Section 4.2, first line: "to specificate" <- to specify

----------------------- REVIEW 3 ---------------------
PAPER: 27
TITLE: Controlling the scope of instances in Haskell
AUTHORS: Marco Silva and Carlos Camarão

OVERALL RATING: -2 (reject)
REVIEWER'S CONFIDENCE: 3 (high)

The initial version of Haskell required an instance declarations to be
in the same module as either the class or the datatype declaration it
related. Haskell 1.3 relaxed the so-called C-T rule and allowed
instance declarations to be placed in arbitrary modules. The designers
wrote: "The visibility of instance declarations presents a
problem. Unlike classes or types, instances cannot be mentioned
explicitly in export lists. Instead of changing the syntax of the
export list, we have adopted a simple rule: all instances are exported
regardless of the export list."

The paper proposes to change the simple rule and to add explicit
import and export declarations for instances. This change would allow
the user to write several C-T instances and to use them selectively.

I am afraid that this creates more problems than it solves. In the
first place, I am not sure how pressing the problem of not being able
to define different instances for the same type actually is. (The
authors do provide some evidence that the current restriction is
limiting, but that says little about the overall impact.) As the
authors point out in Section 3.4, having different instances for the
same type also creates problems, when these instances are used
inconsistently across modules (module A builds a search tree using
ordering X, module B uses the search tree using ordering Y).

If one badly needs to instantiate a method with different bodies, then
using a class is perhaps not a good idea at all, and one should simply
use higher-order functions instead. So thumbs down for the proposal.

----------------------- REVIEW 4 ---------------------
PAPER: 27
TITLE: Controlling the scope of instances in Haskell
AUTHORS: Marco Silva and Carlos Camarão

OVERALL RATING: -3 (strong reject)
REVIEWER'S CONFIDENCE: 3 (high)

This is a discussion paper which proposes making the import and export
of instances explicit. The paper does a reasonable of job of
motivating why it may be important to do so. It makes the obvious
suggestion of how to do so. And it discusses some of the issues that
may arise.

I didn't feel the paper made any new contribution to the understanding
of the issues to do with making the scope of instances explicit and
controllable. In particular, the issue of long exports was only
addressed a comment (suggesting naming the instances -- which, if done
only to handle export lists, feels like a hack), nor were issues of
instance confusion really addressed -- these could be especially tough
in the modern setting of expressive type-level programming with
multi-parameter type classes and functional dependencies as
promulgated by kiselyov et al.  At a higher level, the paper did not
address what could be described as the "philosophy" behind type
classes, which were built with the intent of being used when there was
only a single interpretation of an operator on a type.

Specifics.

P2. In fact, type classes were introduced expressly to remove the need
of having sortBy-like functions where there was a uniform approach to
the behavior on a certain type.

P3. I don't buy your "backwards compatible" argument. If there is a
language pragma, then there is no need to maintain strict backwards
compatitbility.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: instances-communities-report.tex
Type: application/x-tex
Size: 927 bytes
Desc: not available
URL: <http://www.haskell.org/pipermail/ghc-devs/attachments/20141022/69a10c40/attachment-0001.tex>


More information about the ghc-devs mailing list