March 1999 Draft
JavaScript 2.0
Types
|
Wednesday, March 24, 1999
The words type and class are used interchangeably in this specification. A type represents a possibly
infinite set of values. A value can be a member of multiple such sets, so a value can have more than one type. A value
may not have an intrinsic most specific type -- one can ask whether the value v is a member of a given type t,
but this does not prevent the value v from also being a member of some unrelated type s. For example,
null
is a member of type Array
as well as type Function
, but neither Array
nor Function
is a subtype of the other.
On the other hand, a variable does have a particular type. If one declares a variable x of type Array
,
then whatever value is held in x is guaranteed to have type Array
, and one can assign any value of
type Array
to x.
The following types are predefined in JavaScript 2.0:
Type |
Set of Values |
---|---|
void |
undefined |
Null |
null |
boolean |
true and false |
integer |
Double-precision IEEE floating-point numbers that are mathematical integers, including positive and negative zeroes but excluding infinities and NaN |
number |
Double-precision IEEE floating-point numbers, including positive and negative zeroes and infinities and NaN |
character |
Single 16-bit unicode characters |
string |
Immutable strings of unicode characters |
Function |
All functions and null |
array |
All arrays |
Array |
All arrays and null |
type |
All types |
Type |
All types and null |
object |
All values except undefined and null |
Object |
All values except undefined |
Any |
All values |
By convention, predefined types whose names start with an upper-case letter include the value null
, while
predefined types whose names start with a lower-case letter do not include null
. User-defined type names do not
have to follow this convention.
Unlike in JavaScript 1.x, there is no distinction between objects and primitive values. All values can have methods. Some values can be sealed, which disallows addition of ad-hoc properties. User-defined classes can be made to behave like primitives.
The above type names are not reserved words. They are considered to be defined in a scope that encloses a package's global scope, so a package could use these type names as identifiers. However, defining these identifiers for other uses might be confusing because it would shadow the corresponding type names (the types themselves would continue to exist, but they could not be accessed by name).
The names Boolean
, Number
, and String
have been deliberately left unused to enable
implementations to use them to emulate the behavior of the JavaScript 1.x Boolean
, Number
, and String
wrapper objects. These are not part of JavaScript 2.0, but an implementation may support them for compatibility.
The name function
could not be used to mean "all functions" because it is a reserved word. Use Function^*
instead.
A literal number that has an integral value has type integer
; otherwise it has type number
. integer
is a subtype of number
, so every integer
value is also a number
value. A literal string
that has exactly one 16-bit unicode character has type character
; otherwise it has type string
.
character
is a subtype of string
, so every character
value is also a string
value.
Any class defined using the class
declaration is also a type that denotes the set of all of its and its descendants'
instances. These include the predefined classes, so Object
, Date
, etc. are all types. null
is an instance of a user-defined class c if it is an instance of any of c's superclasses.
We can use the following operators to construct more complex types. t and u are type expressions in the expressions below.
Type | Values |
---|---|
t | * |
null and all values of type t |
t ^ * |
All values of type t except null |
t | ? |
undefined and all values of type t |
t ^ ? |
All values of type t except undefined |
t | u |
All values belonging to either type t or type u or both |
t & u |
All values simultaneously belonging to both type t and type u |
The language does not syntactically distinguish type expressions from value expressions, so a type expression can also
use any other value operators such as !
, +
, and .
(member access). Except for parentheses,
most of them are not very useful, though.
We write a b to denote that a is a subtype of b. Subtyping is transitive, so if a b and b c then a c is also true. Subtyping is also reflexive: a a.
The following subtype and type equivalence relations hold. t, u, and v represent arbitrary types.
t t |
u |
t & u
t |
t | t = t |
t & t = t |
t | u = u | t |
t & u = u & t |
(t | u) | v = t |
(u | v) |
(t & u) & v = t &
(u & v) |
t | * = t | Null |
t | ? = t | void |
integer number
object |
character string
object |
boolean object |
array object |
type object |
|
Array = array | Null |
Type = type | Null |
Object = object | Null |
|
t Any |
We write v t to indicate that v is a value that is a member of type t. The following subtyping rule holds: if v t and t s, then v s holds as well. Any particular value v is simultaneously a member of many types.
Types are generally used to restrict the set of objects that can be held in a variable or passed as a function argument. For example, the declaration
var integer x;
restricts the values that can be held in variable x
to be integers.
A type declaration never affects the semantics of reading the variable or accessing one of its members. Thus, as
long as expression new MyType()
returns a value of type MyType
, the following two code snippets
are equivalent:
var MyType x = new MyType(); x.foo();
var x = new MyType(); x.foo();
This equivalence always holds, even if these snippets are inside the declaration of class MyType
and foo
is a private field of that class. As a corollary, adding true type annotations does not change the meaning of a program.
A type is also a value (whose type is type
) and can be used in expressions, assigned to variables, passed
to functions, etc. For example, the code
const type Z = integer; function abs_val(Z i) Z { return i<0 ? -i : i; }
is equivalent to:
function abs_val(integer i) integer { return i<0 ? -i : i; }
As another example, the following method takes a type and returns an instance of that type:
method QueryInterface(type t) t {
... }
Coercions can take place in the following situations:
@
t operator.In any of these cases, if v t, then
v is passed unchanged. If v t,
then an error occurs unless v is undefined
, in which case the following coercions are tried, in order:
Null
t, then null
is used instead of undefined
.boolean
t, then false
is used instead of undefined
.integer
t, then +0.0
is used instead of undefined
.string
t, then ""
is used instead of undefined
.If none of the coercions succeeds, an error occurs.
Some types such as machine integers define additional coercions. These are listed along with descriptions of these types.
@
OperatorOne can explicitly request a coercion in an expression by using the @
operator. This operator has the same
precedence as .
and coerces its left operand to the right operand, which must be a type. ... v@
t ...
can be used in an expression and has the same effect as:
function coerce_to_
t(
t a)
t {return a} //
Declared at the top level
... coerce_to_
t(
v)
...
assuming that coerce_to_
t is an identifier not used anywhere else. The @
operator is useful as a type assertion as in w@Window
. It's a postfix operator to simplify cascading expressions:
w@Window.child@Window.pos
is equivalent to:
(((w@Window).child)@Window).pos
A type cast performs more aggressive transformations than a type coercion. To cast a value to a given type, we use the type as a function, passing it the value as an argument:
type(
value)
For example, integer(258.1)
returns the integer 258
, and string(2+2==4)
returns
the string "true"
.
Need to specify the semantics of type casts. They are intended to mimic the current ToNumber, ToString, etc. methods.
Would we rather have the colon syntax for declaring types? Two sample declarations would be:
var x:integer = 7; function f(a:integer, b:Object):number {...}
A few considerations:
{a:17, b:33}
).
The latter would present a conundrum if we ever wanted to declare field types in an object literal. Some users have
been using these as a convenient facility for passing named arguments to functions.field
be a reserved word.Do we want to make type expressions have a distinct syntax from value expressions? I have not heard any "pro" arguments. Here are the "con" arguments:
(expr1)(expr2)
,
is expr1
a type or a value expression? If the two have the same syntax, it doesn't matter.
Waldemar Horwat Last modified Wednesday, March 24, 1999 |