qualified imports, PVP and so on

Michael Snoyman michael at snoyman.com
Wed Feb 26 10:42:28 UTC 2014

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
> >> >> 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.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/libraries/attachments/20140226/4343dfa6/attachment.html>

More information about the Libraries mailing list