qualified imports, PVP and so on

Johan Tibell johan.tibell at gmail.com
Wed Feb 26 12:03:45 UTC 2014

I think we should relax the PVP requirement to bump the major version
number when adding an instance and instead require that the major version
bump is only required when using orphan instances and otherwise only a
minor version bump is required. Unless I missed some case, code that
depends on a library that follows this rule should not not break.

Here's my reasoning:

If you add a non-orphan instance, it must be because

 * you define the data type or the type class in your package and
 * depend on a package that declares the other entity.

Therefore, no package that depend on your package can declare a non-orphan
instance that could collide with the instance you declare.

On Wed, Feb 26, 2014 at 12:57 PM, Edward Kmett <ekmett at gmail.com> wrote:

> Michael,
> Technically it'd have to do a major version bump to add the instance.
> Herbert,
> That said, transitively bumping all the dependent packages whenever anyone
> upstream's guts change in ways you may never know about isn't a palatable
> option. It requires every user of every library to know about every
> instance or import statement that could transitively drag along an orphan
> even in private modules. This just isn't a realistic model of user behavior.
> I'm unwilling to accept the corollary that I cannot be compatible with
> both the current release of a package and the current platform if there
> have been any instances added in between.
> If the user wants to lock down the instances they can fix the version of
> the upstream dependency that is supplying it.
> The way I see it, if I don't supply the data type, and I don't supply the
> class, then using a class is fine in my API across major versions.
> Otherwise nobody can ship anything that crosses more than even one base
> version let alone versions of other packages.
> The only breakages that can occur be introduced are all due to orphan
> instances.
> What you propose when carried through to its logical conclusion would
> basically kill all development between platform releases.
> -Edward
> On Wed, Feb 26, 2014 at 5:42 AM, Michael Snoyman <michael at snoyman.com>wrote:
>> On Wed, Feb 26, 2014 at 12:22 PM, Herbert Valerio Riedel <hvr at gnu.org>wrote:
>>> On 2014-02-26 at 10:45:40 +0100, Michael Snoyman wrote:
>>> [...]
>>> >> >> ...are you simply referring to the fact that in order to guarantee
>>> >> >> PVP-semantics of a package version, one has to take care to
>>> restrict the
>>> >> >> version bounds of that package's build-deps in such a way, that
>>> any API
>>> >> >> entities leaking from its (direct) build-deps (e.g.  typeclass
>>> instances
>>> >> >> or other re-exported entities) are not a function of the "internal"
>>> >> >> degree of freedoms the build-dep version-ranges provide? Or is
>>> there
>>> >> >> more to it?
>>> [...]
>>> >> From my point of view, I'd argue that
>>> >>
>>> >>  a) 'persistent' failed to live up to the "spirit" of the PVP
>>> contract,
>>> >>     i.e. to expose a "contact-surface" which satisfies certain
>>> >>     invariants within specific package-version ranges.
>>> > How would persistent have done better? AFAICT, the options are:
>>> >
>>> > 1. Do what I did: state a true version dependency on monad-logger,
>>> that it
>>> > works with both version 0.2 and 0.3.
>>> Yes, "persistent" itself does in fact work with both major versions of
>>> "monad-logger", but alas the API reachable through depending solely on
>>> "persistent" leaks details of the underlying monad-logger version used.
>>> ...but the PVP's primary statement is define how a package shall behave
>>> from the point of view of its users (where by user I mean package
>>> build-depending on `persistent`). So...
>>> > 2. Constrain it to one or the other, which would be a falsehood that
>>> would
>>> > restrict users' ability to use the package.
>>> ...this is would actually be, what I'd interpret the PVP to
>>> expect/require from "persistent" in order to satisfy its goal to shield
>>> the package's users from incompatible changes.
>>> > Let's try it a different way. If transformers removed a MonadIO
>>> instance
>>> > between version 2 and 3 of the library, should that mean that every
>>> single
>>> > package with type signatures involving MonadIO should be constrained
>>> to one
>>> > specific version of transformers?
>>> yes, that'd be what I'm suggesting here (the [1] footnote is a different
>>> suggestion for the same problem though)
>> So let's analyze how far you want to go here. Imagine if version 0.3.0 of
>> transformers did not have a MonadIO instance for StateT, and version 0.3.1
>> added it. Now some library has a function:
>> myFunc :: MonadIO m => Int -> m String
>> What versions of transformers is it allowed to work with? If it allows
>> version 0.3.0 and 0.3.1, and a user depends on the presence of the MonadIO
>> StateT instance, the build can be broken by moving back to version 0.3.0
>> (which may be demanded by some other packages dependencies). This is simply
>> the reverse of the monad-logger situation, where an instance was added
>> instead of being removed. I don't see a reasonable solution to this
>> situation... well, besides everyone just trusting a curator to build all of
>> these packages for them.
>> And just to be clear: if persistent had bumped its lower version bound on
>> monad-logger, then users still on the old version of monad-logger would be
>> unable to upgrade, and for no real reason. persistent would have required a
>> major version bump most likely[1], which would have caused all packages
>> downstream from it to do version bumps as well.
>> Forgetting about my position as a library author, or as a Stackage
>> maintainer, and speaking purely as a library *user*, this would be a
>> terrible situation to be in.
>> [1] That's another hole in the PVP I think. It doesn't explicitly address
>> the issue of an API change by eliminating compatibility with a previously
>> accepted dependency, but I've seen huge breakages occur due to this.
>>> >>  b) However, the PVP can be blamed as well, as in its current form it
>>> >>     doesn't explicitly address the issue of API-leakage from
>>> transitive
>>> >>     build-dependencies. [1]
>>> >>
>>> >> The question for me now is whether the PVP is fixable in this respect,
>>> >> and at what cost.
>>> >>
>>> >> Moreover, it seems to me, it always comes down to type-class instances
>>> >> causing most problems with the PVP (either by requiring version-bump
>>> >> cascades throughout the PVP-adhering domain of Hackage, or by their
>>> hard
>>> >> hard to constraint leakage through package module/boundaries); maybe
>>> we
>>> >> need address this issue at the language-level and provide some
>>> facility
>>> >> for limiting the propagation of type-class instances first.
>>> >>
>>> >>
>>> > There's one other issue, which is reexports. As an extreme example,
>>> imagine:
>>> >
>>> > * Version 1 of the foo package has the Foo module, and it exports foo1
>>> and
>>> > foo2.
>>> > * Version 2 of the foo package has the Foo module, and it exports foo1.
>>> > * Version 1 of the bar package as the Bar module, defined as:
>>> Yes, I'm well aware of this problem, but that's easier to control, as
>>> you can use explicit import/export lists to constraint what entities you
>>> expose to direct users of your package. That's what I'm doing e.g. in
>>> http://hackage.haskell.org/package/deepseq-generics-
>>> where I'm explicitly naming the entities I re-export from
>>> Control.DeepSeq for convenience. (However, I'm lacking such a facility
>>> for instances)
>> Agreed, the reexport issue is something that can be dealt with, whereas
>> typeclasses don't have such a facility right now. I just wanted to point it
>> out to make sure we were considering all issues.
>>> >
>>> > module Bar (module Foo) where
>>> > import Foo
>>> > * According to the PVP, the bar package can have a version bound on
>>> foo of
>>> > `foo > 1 && < 2.1`.
>>> > * User code that depends on foo2 being exported from Bar will be
>>> broken by
>>> > the transitive update of foo.
>>> >
>>> > The example's extreme, but it's the same basic problem as typeclass
>>> > instances.
>>> [...]
>>> >>  [1]: An alternative to what I'm suggesting in 'a)' (i.e. that it'd be
>>> >>       `persistent`'s obligation), could be that the package you
>>> >>       mentioned (which broke due to monad-logger having a
>>> non-monotonic
>>> >>       API change), might become required to include packages supplying
>>> >>       the instances they depends upon in their build-depends, thus
>>> >>       turning an transitive dep into a direct dependency.
>>> > I don't think I follow this comment, sorry.
>>> I'm basically just saying, that the package which used "persistent",
>>> ought to add "monad-logger ==0.2.*" to its direct build-dependencies, as
>>> it depends on an instance provided by monad-logger. The huge down-side
>>> is, that you'd have to know about type-class instances leaked through
>>> persistent, in order to know that'd you have to add some of
>>> "persistent"'s transitive build-depends to your own package, in order to
>>> safe yourself from missing out on type-class instances.
>> And my approach is that the only sane way to create repeatable builds is
>> to *always* list the exact versions of all packages you depend upon. And in
>> my opinion, it's far more important to ensure that the code behaves the
>> same way than that it simply builds. The only real way to do that is to
>> always use the same versions of all dependencies.
>> Michael
>> _______________________________________________
>> Libraries mailing list
>> Libraries at haskell.org
>> http://www.haskell.org/mailman/listinfo/libraries
> _______________________________________________
> Libraries mailing list
> Libraries at haskell.org
> http://www.haskell.org/mailman/listinfo/libraries
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/libraries/attachments/20140226/5eb1e5ef/attachment-0001.html>

More information about the Libraries mailing list