March 1999 Draft
JavaScript 2.0
Versions
previousupnext

Wednesday, March 24, 1999

Motivation

As a package evolves over time it often becomes necessary to change its exported interface. Most of these changes involve adding symbols (global and class members), although occasionally a symbol 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 symbols. Unless we solve this problem, an author of a library will not be able to add even one symbol in a future version of his library because that symbol 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 namespace and in the namespaces within 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 symbols 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.

Overview

The versioning system in JavaScript 2.0 only affects exports of symbols. The concept of a version does not apply to a package's internal code; it is up to package developers to ensure that newer releases of their packages continue to behave compatibly with older ones.

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 symbols in version V.

Example

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

public version "1.0" = "";       // The = "" makes version "1.0" be the default
public version "2.0" > "1.0";

public var serialNumber;

public function sort(Function compare, any[] array) any[] {...}
public function merge(Function compare, any[] array1, any[] array2) any[] {...}
public "2.0" function stablesort(Function compare, any[] array) any[] {...}

Suppose, further, that client package C1 imports version "1.0" of P, client package C2 simultaneously imports version "2.0" of P, and a search for P yields the latest release described above. There would be only one instance of P running -- the latest release. Both clients would get the same sort and merge functions, and both would see the same serialNumber variable (in particular, if client C1 wrote to serialNumber, then client C2 would see the updated value), but only client package C2 would see the stablesort function. Both clients would get the quicksort release of sort. If client package C1 defined its own stablesort function, then that function would not conflict with P's stablesort; furthermore, P's sort would still refer to P's stablesort in its internal subroutine call.

Had only the first release of P been available, client package C2 would obtain an error because version 2 of P's API would not be available. Client C1 could run normally, although the sort function it calls would use bubble sort instead of the quicksort.

Note that the last release of P did not change the API so it did not need a new version. Of course, it could define a new version if for some reason it wanted clients to be able to demand the last release of P even though its API is the same as the second release.

Version Declarations

Version Names

A version name Version is a quoted string literal such as "1.2" or "Private Interface 2.0". Two version names are equal if their strings are equal. A special version whose name is the empty string "" is called the default version.

Declaration Syntax

A package must declare every version it uses except "", which is declared by default if not explicitly declared. A version must be declared before its first use. A given version name may be declared only once per package. A package declares a version name Version using the version declaration:

VersionDefinition 
   [Visibilityversion Version [> VersionList;
|  [Visibilityversion Version [= Version;
VersionList 
   Version , ... , Version

A version declaration cannot be nested inside a ClassDefinition's Block.

If Visibility is present, it must be either private, package, or public (without VersionsAndRenames). Unlike in other declarations, the default is public, which makes Version accessible by other packages. A private or package Visibility hides its Version from other packages; such a Version can be used only by being included in the VersionList of another Version. Also unlike other declarations, all Version declarations are global.

Version Ordering

If the Version being declared is followed by a > and a VersionList, then the Version is said to be greater than all of the Versions in the VersionList. We write v1 :> v2 to indicate that v1 is greater than v2 and v1 : v2 to indicate that either v1 and v2 are the same version or v1 :> v2. Order is transitive, which means that if v1 :> v2 and v2 :> v3, then v1 :> v3. This order induces a partial order on the set of all versions. It is possible for two versions to be unordered with respect to each other, in which case they are not equal and neither is greater than the other.

If the Version v1 being declared is followed by a = and another Version v2, then v1 becomes an alias for v2, and they may be used interchangeably.

Version Ranges

A VersionRange specifies a subset of all versions. This subset contains all versions that are both greater than or equal to a given Version1 and less than or equal to a given Version2. A VersionRange can have either of the following forms:

VersionRange 
   Version
|  [Version1.. [Version2]

The first form specifies the one-element set {Version}. The second form specifies the set of all Versions v such that v : Version1 and Version2 : v. If Version1 is omitted, the condition v : Version1 is dropped. If Version2 is omitted, the condition Version2 : v is dropped.

Discussion

Version Numbers 1

The original version of this specification allowed both strings and numbers as Version names. Two version names were equal if their toString representations were identical, so version names 2.0 and "2" were identical but 2.0 and "2.0" were not. In addition, numbered versions had an implicit order: For any two versions v1 and v2 whose names could be represented as numbers, v1 :> v2 if and only if v1 was numerically greater than v2. Additionally, every version except 0 was greater than version 0. It was an error to define explicit version containment relations that would violate this default order, directly or indirectly.

Numbered Version names were dropped for simplicity and to avoid confusion with versions such as 1.2.3 (which would be a syntax error unless quoted).

Version Numbers 2

Another, simpler, approach is to require all Version names to be nonnegative integers (without quotes). Versions would not need to be declared, and all versions would be totally ordered in numerical order. A disadvantage of this approach is that the total order keeps versions from being branched.

Dynamic Version Definitions

Currently version definitions are fixed. These could be turned into function calls that define versions and list their relationships. If we can get a variable or constant to hold a set of version names, then we could use these variables rather than specific version names in the VersionsAndRenames lists after public keywords. This would provide another level of abstraction and flexibility.

Separate Version Definitions

Yet another approach is to consolidate all of the information in VersionsAndRenames into a set of export statements, say, at the top of the file rather than being interspersed throughout a package along with public declarations. This would make it easier to see all of the identifiers exported by a particular version of the package, but it would also likely lead to inconsistencies when someone forgets to update an export statement after inserting another variable, function, field, or method definition. Such errors would likely be caught after a package has been released.


Waldemar Horwat
Last modified Wednesday, March 24, 1999
previousupnext