March 1999 Draft
JavaScript 2.0
Versions
|
Wednesday, March 24, 1999
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.
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
.
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:
com_netscape_length
method while MIT's objects used the edu_mit_length
method.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.
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.
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.
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.
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.
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:
version
Version [>
VersionList] ;
version
Version [=
Version] ;
,
... ,
VersionA 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.
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.
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:
..
[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.
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 number
s,
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).
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.
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.
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 |