April 2002 Draft
JavaScript 2.0
Rationale
Miscellaneous
|
Wednesday, December 5, 2001
This section presents a number of miscellaneous alternatives that were considered while developing this proposal.
Object
The root of the type hierarchy was chosen to be the existing JavaScript 1.5 type Object
.
Primitive numbers, strings, and booleans were made into instances of subclasses of Object
and the existing wrapper
classes eliminated, thereby eliminating the confusing distinction between boolean primitives and objects, etc. in JavaScript
1.5.
The alternative was to define a new root type, any
, that is an ancestor of Object
as well as
primitive numbers, strings, and booleans, which could have their own types number
, string
, and boolean
respectively. In this scenario the JavaScript 1.5 classes Number
, String
, and Boolean
would still be subclasses of Object
, but the primitive values could not be members of these classes — for
example, false
would be a member of the class boolean
but not Boolean
. The object obtained
by boxing false
would be a member of the class Boolean
but not boolean
and would evaluate
to true (!) if used in an if
statement.
The any
alternative was more compatible with JavaScript 1.5, but the added complexity and confusion was not
deemed worthwhile for the extra compatibility. Instead, JavaScript 2.0 simplifies and reguralizes the behavior of JavaScript
1.5 in this area.
Never
and Void
The types Never
and Void
are different and serve different purposes. When used as a return type, Never
describes the inability to return
from a function, while Void
states that the function returns, but the value returned is not useful (it’s always
undefined
).
The following example illustrates the use of Never
and Void
:
// This function returns no useful value. function display(message:String):Void { document.write("<\_P>" + message + "<\/P>\n"); } // This function cannot return. function abort(message:String):Never { display(message); throw AbortException; } function chickenCount(myChickens:Array[Chicken]):Integer { if (notHatched(myChickens)) abort("Can’t count the chickens yet"); else return myChickens.length; }
Note that if the function abort
had no explicit return type or any return type other than Never
,
then the compiler would likely issue a warning inside the function chickenCount
because it contains a code path
(the false case of its if
) that appears to fall out of the function without returning a value, while chickenCount
is declared to return an Integer
. The Never
return type on abort
tells the compiler
that there is no such code path inside chickenCount
.
It might be a good idea for a compiler to issue a warning for a function that is declared as returning type Never
for which the compiler can’t verify that the function can’t return.
We could define other type operators such as the ones in the table below. s and t are type expressions.
Type | Values | Implicit coercion of value v |
---|---|---|
t[] |
null as well as nonresizable arrays of values of type t |
undefined null |
const t |
Makes type t, which must be an array type, into a read-only array type | None |
+ t |
null or any value belonging to type t |
null null ; undefined
null (if undefined is not a
member of t); any other implicit coercions already defined for t |
s + t |
All values belonging to either type s or type t or both | If vs+ t,
then use v; otherwise, if v as s is defined then use v as s;
otherwise, if v as t is defined then use v as t. |
s * t |
All values simultaneously belonging to both type s and type t | If v as s as t is defined
and is a member of s* t, then use v as s as t. |
s / t |
All values belonging to type s but not type t | If v as s is defined and is a member of s/ t,
then use v as s. |
The []
suffix operator could be used to make array types. Unlike Array
, these arrays would be
dense, indexable only by integers, nonresizable, and non-subclassable — t[]
has the class
modifier final
.
A new unary operator, const
, could be added. This operator would
take a PostfixExpressionOrSuper x
as an operand. If x is an instance of a non-ConstArray
array, const
x
would return a ConstArray[
T]
copy of x, where T is the most specific
type such that every element of x is a member of T. If x is an array type, const
x
would be the corresponding ConstArray
type.
[]
is the type of writable arrays of t.const
t[]
is the type of constant arrays of t.[][]
is the type of writable arrays of writable arrays of t.const
t[][]
is the type of constant arrays of writable arrays of t
— const
binds looser than []
so it’s the same as const (
t[])[]
.(const
t[])[]
is the type of writable arrays of constant arrays of t.Definitions of top-level entities in a package can only be placed in the namespace public
or in namespaces
(including internal
) defined within that package. This restriction does not apply to non-top-level definitions
such as those of class members (both instance and static), or local definitions within blocks.
The reason for this restriction is to prevent irresolvable name clashes when importing a package. Suppose that package
P defined a namespace N and defined a top-level entity N::
n. Package
Q could then import package P and define its own top-level entity, also named N::
n.
Package R could not import both package P and package Q because they define two top-level
entities, both named N::
n.
Note that this is not an issue for public
top-level definitions because an import statement always moves imported
public
top-level definitions into their own namespace, which is then use
d.
primitive
A primitive
class modifier attribute was considered.
This attribute would exclude null
from the set of values that can be stored in variables typed with this class:
if a class C is defined using the primitive
attribute, then null
is not considered to
be a member of the type C (there would have to be a way to specify a default value for uninitialized variables
of type C). This attribute would permit user-defined classes to behave like some predefined classes such as Number
.
This attribute was dropped for now because of circularity problems with classes that haven’t been defined yet. If class
C hasn’t been defined yet, one can still create a variable of type C; such a variable is initialized
to null
. If C turned out to be a primitive class then the variable’s value would need to be retroactively
changed.
Most operators can be overridden. However, the !
,
||
, ^^
, &&
, and ?:
operators cannot be overridden directly; instead,
they are affected by overrides of toBoolean
. The reason for not overriding ||
, &&
,
and ?:
is that they do not always evaluate all of their arguments, and programmers often take advantage of this
short-circuiting behavior. !
is not overridable in order to preserve useful identities such as:
if (!
x)
A else
B
if (
x)
B else
A!(
x &&
y)
!
x || !
y !=
y
!(
x ==
y)
The for-in operator can be customized using the three methods Iterator::forIn
,
Iterator::next
, and Iterator::done
. There are a few design decisions that come into play here:
Iterator::forIn
take a parameter
f and have it call f for each element in the collection)? The reason is that the iterator model
permits future extensions such as iterating through two collections concurrently. This cannot be done using callbacks.Iterator::forIn
and Iterator::next
instead of having Iterator::forIn
and Iterator::next
return just an iterator and having a separate Iterator::get
method that,
given an iterator, returns an element? The rationale here is that the separate Iterator::get
method would
introduce unavoidable concurrency hazards. Since null
(or any other value) can be a member of a collection,
we can’t use the return value from Iterator::get
to detect the end of iteration; instead, we’d have to use
a null
return value from Iterator::forIn
or Iterator::next
to indicate the end
of iteration. In such a case, however, it would be possible for another thread to delete the last element between the
call to Iterator::forIn
or Iterator::next
and the call to Iterator::get
, and Iterator::get
would then be stuck. We could use an exception to indicate the end of iteration, but this would put exception handling
on the fast path of program execution.Iterator::done
? Some objects may erect temporary scaffolding to support iteration. This method
informs them that they can take down such scaffolding. Since JavaScript currently lacks finalization, there is no other
way for the iterated object to know that the iteration aborted early — the start of another iteration cannot be
used as an indication that the previous iteration completed because several iterations can be happening simultaneously
on the same object.Earlier drafts of this proposal had a .class
operator that returned an instance’s class. This operator was
defined using the grammar production:
The operator was removed because obtaining an instance’s class breaks abstraction. The client of a factory method m could determine whether m was returning instances of some stated class C or subclasses of C, and m could not evolve to alter its implementation decision of what precise instances to return without potentially breaking clients.
Waldemar Horwat Last modified Wednesday, December 5, 2001 |