April 2002 Draft
JavaScript 2.0
Rationale
Versioning
previousupnext

Tuesday, October 9, 2001

Motivation

As a package evolves over time it often becomes necessary to change its exported interface. Most of these changes involve adding definitions (top-level or class members), although occasionally a definition may be deleted or renamed. In a monolithic environment where all JavaScript source code comes preassembled from the same source, this is not a problem. On the other hand, if packages are dynamically linked from several sources then versioning problems are likely to arise.

One of the most common avoidable problems is collision of definitions. Unless we solve this problem, an author of a library will not be able to add even one definition in a future version of his library because that definition’s name could already be in use by some client or some other library that a client also links with. This problem occurs both in the global scope and in the scopes of classes from which clients are allowed to inherit.

Example

Here’s an example of how such a collision can arise. Suppose that a library provider creates a library called BitTracker that exports a class Data. This library becomes so successful that it is bundled with all web browsers produced by the BrowsersRUs company:

package BitTracker;

public class Data {
  public field author;
  public field contents;
  function save() {...}
};

function store(d) {
  ...
  storeOnFastDisk(d);
}

Now someone else writes a web page W that takes advantage of BitTracker. The class Picture derives from Data and adds, among other things, a method called size that returns the dimensions of the picture:

import BitTracker;

class Picture extends Data {
  public method size() {...}
  field palette;
};

function orientation(d) {
  if (d.size().h >= d.size().v)
    return "Landscape";
  else
    return "Portrait";
}

The author of the BitTracker library, who hasn’t seen W, decides in response to customer requests to add a method called size that returns the number of bytes of data in a Data object. He then releases the new and improved BitTracker library. BrowsersRUs includes this library with its latest NavigatorForInternetComputing 17.0 browser:

package BitTracker;

public class Data {
  public field author;
  public field contents;
  public method size() {...}
  function save() {...}
};

function store(d) {
  ...
  if (d.size() > limit)
    storeOnSlowDisk(d);
  else
    storeOnFastDisk(d);
}

An unsuspecting user U upgrades his old BrowsersRUs browser to the latest NavigatorForInternetComputing 17.0 browser and a week later is dismayed to find that page W doesn’t work anymore. U’s granddaughter Alyssa P. Hacker tries to explain to U that he’s experiencing a name conflict on the size methods, but U has no idea what she is talking about. U attempts to contact the author of W, but she has moved on to other pursuits and is on a self-discovery mission to sub-Saharan Africa. Now U is steaming at BrowsersRUs, which in turn is pointing its finger at the author of BitTracker.

Solutions

How could the author of BitTracker have avoided this problem? Simply choosing a name other than size wouldn’t work, because there could be some other page W2 that conflicts with the new name. There are several possible approaches:

The last approach appears to be the most desirable because it places the smallest burden on casual users of the language, who merely have to import the packages they use and supply the current version numbers in the import statements. A package author has to be careful not to disturb the set of visible prior-version definitions when releasing an updated package, but authors of dynamically linkable packages are assumed to be more sophisticated users of the language and could be supplied with tools to automatically check updated packages’ consistency.

Versioning in JavaScript 2.0

JavaScript 2.0 employs namespaces to provide safe versioning. A package can export several namespaces, each of which provides a different view of the package’s contents. Each namespace corresponds to a version of the package’s API.

Terminology

A version describes the API of a package. A release refers to the entirety of a package, including its code. One release can export many versions of its API. A package developer should make sure that multiple releases of a package that export version V export exactly the same set of definitions in version V.

Example

As an example, suppose that a developer wrote a sorting package Sorter with functions sort and merge that called bubble sort in the initial version. In the next release the developer adds a function called stablesort and includes it in version V2. In a subsequent release V3, the developer changes the sort algorithm to a quicksort that calls stablesort as a subroutine and adds the permute function. That last release of the package might look like:

package Sorter {
explicit namespace V2;
explicit namespace V3;
internal const V2and3 = V2 V3;

public var serialNumber;

public function sort(compare: Function, array: Array):Array {...}
public function merge(compare: Function, array1: Array, array2: Array):Array {...}
V2and3 function stablesort(compare: Function, array: Array):Array {...}
V3 function permute(array: Array):Array {...}
}

Suppose, further, that client packages C1 and C2 both import Sorter. There is only one instance of Sorter running — the latest release. By default, both C1 and C2 see only Sorter’s original API. However, suppose that C2 is aware that Sorter has been extended and would like to also use some of its newer functionality. To do this, C2 evaluates the magic incantation

use namespace(Sorter.V2);

after it imports Sorter. This enables C2 to also see the stablesort function. Note that, in this example, both clients see the same sort and merge functions and the same serialNumber variable (in particular, if C1 wrote to serialNumber, then C2 would see the updated value), but only C2 can see the stablesort function. Both clients get the quicksort release of sort. If client package C1 defined its own stablesort function, then that function would not conflict with Sorter’s stablesort; furthermore, Sorter’s sort would still refer to Sorter’s stablesort in its internal subroutine call.

Had only the first release of Sorter been available, client C2 would obtain an error because Sorter.V2 would be undefined. Client C1 could run normally, although the sort function it calls would use bubble sort instead of the quicksort.

The example above illustrates versioning as it applies to a package’s globals. The same techniques can be used to add members to existing classes, and there versioning is much more useful.


Waldemar Horwat
Last modified Tuesday, October 9, 2001
previousupnext