[plt-scheme] On PLaneT packages and compatible upgrades

From: Eli Barzilay (eli at barzilay.org)
Date: Wed May 10 12:15:09 EDT 2006

On May 10, Jacob Matthews wrote:
> I'm not a big fan of this proposal --- programmer-run wizards to
> generate wrappers strike me as a red flag.

Me too.  (It's a good idea to have them around as checkers, but not
code generators.)


> Here's a counterproposal that seems simpler to me:
> 
> 1. Adding exports to a program remains a compatible upgrade. When a
> package author submits an upgraded package, the PLaneT server
> automatically extracts an "interface file" that contains the
> package's directory structure, and for each Scheme module in that
> directory structure its list of provided names. It checks this
> interface file against the interface file for the old version of the
> package and makes sure that the new one is a proper superset of the
> old (i.e. it has all the files and directories the old one had, and
> for each module the new version provides a superset of the names the
> old one provided).

And what happens when I want to rename or remove a binding?  Usually
it will be easy to translate the new interface to the old one.  What I
generally don't like is getting attached to the set of provided names
as the major feature for determining compatibility.





> A somewhat nice side benefit is that I could allow "power users" to
> write their own interface files and use those instead of the
> automatically-generated ones.  This would let a programmer separate
> out a package's interface from its implementation in a nicer way
> than just creating a directory called "private" and hoping nobody
> starts relying on individual files in there.

This sounds like a bad idea -- you're crating a solution at the wrong
level.


On May 10, Matthew Flatt wrote:
> 
> It sounds like you need to provide a translation of my translation
> of Matthias's original message.

(See below.)


> I mean that version N.(M+1) exists only because the author of the
> library explicitly decides to implement it. And if it's a trivial
> implementation in terms of version (N+1).0, then the library author
> can automate the implementation by running a tool. In no other
> sense, however, does version N.(M+1) exist automatically.

(OK, but I still don't like the based-on-names only solution...)


> > What I would prefer is that every time I release a new version
> > N+1, I provide my own version N compatibility layer in the form of
> > a new module
> 
> Exactly.

So if you're not talking about doing anything automatically, then
we're closer than I thought.  What I do is

(a) assume that the purpose of a package stays the same

(b) flatten the version numbers into a flat line of version numbers,
    no branches and no distinction beween a new major or minor version

The second item makes things very simple, but it needs (a) which
implies that any new version makes it easy to implement the previous
version.  (The translation of your translation is lost because of
(b).)

As a demonstration, I begin with a module

  (module foo mzscheme
    (provide foo bar)
    (define (foo x) (+ x 1))
    (define (bar x) (* x 2)))

In the next version, I have this:

  (module foo mzscheme
    (provide foo bar blah)
    (define (foo x n) (+ x n)) ; added a second argument
    (define (bar x) (* x 2))
    (define blah 3.14)) ; new binding

and I need to also write:

  (module foo-prev mzscheme
    (require "foo.ss")
    (define (old-foo x) (foo x 1))
    (provide (all-from-except "foo.ss" foo blah)
             (rename old-foo foo)))

In the next one I make the second argument for `foo' default to the
first one, eliminating the need for `bar':

  (module foo mzscheme
    (provide foo blah)
    (define foo
      (case-lambda [(x) (foo x x)]
                   [(x n) (+ x n)]))
    ;; no `bar' -- use `foo' instead
    (define blah 3.14)
  )

  (module foo-prev mzscheme
    (require "foo.ss")
    (define bar foo)
    (provide (all-from "foo.ss") bar))

And then I realize that 3.14 is a bug and fix that

  (module foo mzscheme
    (provide foo blah)
    (define foo
      (case-lambda [(x) (foo x x)]
                   [(x n) (+ x n)]))
    (define blah 3.141592653589793)
  )

and now in my foo-prev module I can decide to provide the old value
(making the new value a change in behavior), or I can decide that it
is strictly a bug and do nothing about it.

Each time I submit a new version, planet can use the -prev modules to
provide all previous versions.

Given assumption (a), such -prev modules should always be easy to
generate.  Easy enough that they can be automatically constructed by
planet, using some simple information in info.ss:

  (module info (lib "infotab.ss" "setup")
    (define name "foo")
    ...
    (define version "3")
    (define change-log
      '(("3" "4" no-changes)
        ("2" "3" (removed bar)
                 (changed (bar x) (foo x)))
        ("1" "2" (changed (foo x) (foo x 1))
                 (added   blah)))))

or, if I want old versions to still get 3.14 for blah:

  (module info (lib "infotab.ss" "setup")
    (define name "foo")
    ...
    (define version "3")
    (define change-log
      '(("3" "4" ; to get the old blah, use 3.14
                 (changed blah 3.14))
        ("2" "3" (removed bar)
                 (rewrite (bar x) (foo x)))
        ("1" "2" (rewrite (foo x) (foo x 1))
                 (added   blah)))))

-- 
          ((lambda (x) (x x)) (lambda (x) (x x)))          Eli Barzilay:
                  http://www.barzilay.org/                 Maze is Life!


Posted on the users mailing list.