Package "mounting" proposal

Sven Moritz Hallberg sm at khjk.org
Sat Jul 15 19:24:43 EDT 2006


Hi lists,

(I hope my cross-posting is okay, but somehow this post seems to apply to
all of you, so here goes...)
 
I've recently noticed that folks at GHC HQ are working on a way to resolve
the problem of importing two modules with the same name from different
packages. There is a proposal[1] on the GHC wiki calling for a syntax
extension for 'import' statements in Haskell modules so that the package
(and version) to import from can be specified explicitly. There is a
second ("extended") proposal[2], which calls for the ability to import
(subtrees of) the module hierarchy exposed by a package and attach it to
the global module namespace at an arbitrary point, analogous to mounting a
filesystem in Unix. This proposal was appearently inspired by a post to the
libraries mailing list by Frederik Eaton[3].

I agree with Frederik that it would be very nice to remove the burden of
writing out long package or hierarchy prefixes in modules, and just work
in some previously defined context. I'd like to propose yet another
alternative to the existing two proposals that follows [2] in trying to
satisfy [3] but differes from it in the following ways:

  - It doesn't require an extension of the existing Haskell syntax.
  - It can be implemented by extending Cabal alone (AFAICT).

In particular, I propose to drop the assumption that Haskell modules are
closed entities but rather always consider them to be seen in the context
of a particular _package_. The package is now made responsible for
assembling an appropriate (hierarchical) module namespace to which imports
in the packaged modules are taken to refer. To this end, the current
'depends:' entry in a package description would be replaced by a more
general "mounting" construct, i.e. "mount package foo at Foo.Bar in the
module hierarchy". Optionally, as in [2], only a subtree of "foo" could be
selected, or only a specific version of "foo".

While I'm at it, I had to evaluate how this proposal would interact with
the "ECT" module versioning scheme, I've proposed earlier[4]. I'd like to
rework that scheme to this proposal. In ECT, a library author guarantees
to his users that their imports will never break by providing different
(numbered) variants of the library modules whenever their interface
changes. By keeping the old variants as re-exports of updated versions,
the author can record compatibilities. This carries the burden for users
to annotate each import with a version number. If we lift the principles
of ECT to the package level in accord with this proposal, that burden
largely evaporates. Keeping the promise of "eternal
backwards-compatibility", however, requires an (obvious) extension to the
way Cabal deals with version numbers...

First of all, package "mounts" must be able to say "this version, or any
compatible one". Obviously, that's actually always what one wants, so it
can be taken to be the only meaning of specifying "mount foo-1.3 at
Foo.Bar". Then the question is just when to consider another version of
foo compatible to 1.3. Obviously, only later versions should be
considered, but when does compatibility end? Only the author of foo knows!
She must specify it somehow. Two possibilities come to mind:

  1. Add a field to the package description of foo (v1.4, say) that says
     "I'm backwards-compatible with 1.3." When building, this relation
     would have to be inspected to see whether any currently installed
     version of foo satisfies the dependency specified by the mount.
  2. Declare a convention for version numbers to carry compatibility
     information, like the OpenGL standard, for example: If the new
     version is backwards-compatible, only the minor version number
     changes. If it isn't, the major version number must be incremented.

I'd personally favour 2, as it would be easier to maintain for the author
of foo (no compatiblity-field in the package description to update all the
time) and also to implement in the build system (just a simple version
comparison instead of a relation traversal to check a dependency).

As a (more conservative?) variant of alternative 2, the third (instead of
second) version component could be declared as "stays compatible", i.e. if
only the third (or later) version component changes, the new version is
assumed backwards-compatible, otherwise it's assumed incompatible. This
would allow frequent incompatible updates without quickly getting large major
version numbers...

As for wasting space by keeping old versions around: Of course, if the new
one is backwards-compatible, any old version can be removed. Otherwise, if
it's "half-way" compatible, the author of foo can release a new
backwards-compatible revision of the old version that reimplements (part
of) the functionality in terms of the new operations, so the old version
can be replaced by this leaner revision.


Comments?

Best regards,
Sven

[1]: http://cvs.haskell.org/trac/ghc/wiki/GhcPackages
[2]: http://cvs.haskell.org/trac/ghc/wiki/GhcPackageNamespaces
[3]: http://www.haskell.org/pipermail/libraries/2005-June/004009.html
[4]: http://www.haskell.org/tmrwiki/EternalCompatibilityInTheory


More information about the Libraries mailing list