ECMAScript 4 Netscape Proposal
Rationale
Operators
|
Thursday, May 22, 2003
User-overridable operators had originally been part of the ECMAScript 4 proposal but were deferred to a future version of the language in order to keep ECMAScript 4 small. If implemented, operator overriding might behave as described in this section.
ECMAScript 4 provides a number of operators such as such as +
, *
, a[
b]
,
and -=
. These operators are dispatched based on the types of their arguments. Some operators are dispatched only
on one operand, in which case the dispatch behaves like property lookup. Other operators are dispatched on two operands simultaneously.
Finally, some operators are syntactic sugars for combinations of existing operators.
There are predefined definitions for all ECMAScript operators. In addition, a class may provide additional definitions by overriding the existing behavior. Such overrides apply only when at least one of the dispatched operands is an instance of that class or its descendants. Operator overriding is useful, for example, to implement units without having to add units to the core of the ECMAScript 4 language.
This section presents the expansions of operators in ECMAScript source code, the built-in definitions of those operators, and the means of overriding them.
When an operator is invoked in an expression, the corresponding operator is invoked according to the table below. The operator invocations use pseudo-syntax to denote which operator is called; only the expression syntax on the left side of the table can be used to invoke operators.
Expression | Operator Invocation |
---|---|
+ x |
operator "+"( x) |
- x |
operator "-"( x) |
~ x |
operator "~"( x) |
++ x |
( x = operator "++"( x)) |
x++ |
( = x, x = operator "++"( x), ) ,
where is a temporary variable |
-- x |
( x = operator "--"( x)) |
x-- |
( = x, x = operator "--"( x), ) ,
where is a temporary variable |
x( a1, ..., an) |
operator "()"( x, a1, ..., an) |
new x |
operator "new"( x) |
new x( a1, ..., an) |
operator "new"( x, a1, ..., an) |
x[ a1, ..., an] |
operator "[]"( x, a1, ..., an) |
x[ a1, ..., an] = y |
operator "[]="( x, y, a1, ..., an) |
delete x[ a1, ..., an] |
operator "delete[]"( x, a1, ..., an) |
x + y |
operator "+"( x, y) |
x - y |
operator "-"( x, y) |
x * y |
operator "*"( x, y) |
x / y |
operator "/"( x, y) |
x % y |
operator "%"( x, y) |
x << y |
operator "<<"( x, y) |
x >> y |
operator ">>"( x, y) |
x >>> y |
operator ">>>"( x, y) |
x < y |
operator "<"( x, y) |
x > y |
operator "<"( y, x) |
x <= y |
operator "<="( x, y) |
x >= y |
operator "<="( y, x) |
x in y |
operator "in"( x, y) |
x == y |
operator "=="( x, y) |
x != y |
!( operator "=="( x, y)) |
x === y |
operator "==="( x, y) |
x !== y |
!( operator "==="( x, y)) |
x & y |
operator "&"( x, y) |
x ^ y |
operator "^"( x, y) |
x | y |
operator "|"( x, y) |
x += y |
( x = operator "+"( x, y)) |
x -= y |
( x = operator "-"( x, y)) |
x *= y |
( x = operator "*"( x, y)) |
x /= y |
( x = operator "/"( x, y)) |
x %= y |
( x = operator "%"( x, y)) |
x <<= y |
( x = operator "<<"( x, y)) |
x >>= y |
( x = operator ">>"( x, y)) |
x >>>= y |
( x = operator ">>>"( x, y)) |
x &= y |
( x = operator "&"( x, y)) |
x ^= y |
( x = operator "^"( x, y)) |
x |= y |
( x = operator "|"( x, y)) |
Unary operators are single-dispatched just like regular methods. in
is like a unary operator in that it can
only be overridden by its right operand’s class, and it is single-dispatched by its right operand. Like in regular method
dispatch, null
is considered to be a member of only the types Null
and Object
.
Binary operator methods are double-dispatched based on the types of both operands. When one of the binary operators
is invoked in an expression a b,
all applicable operator "
"(
x,
y)
methods are examined to find the most specific one, which is then called. A definition of operator "
"(
x:
X,
y:
Y)
is applicable to a b
if the value of a is a member of X and the value of b is
a member of Y, except that, like in regular method dispatch, null
is considered to be a member of only the types Null
and Object
. A definition of operator "
"(
x:
X,
y:
Y)
is most specific if it is applicable and if every applicable definition of operator "
"(
x:
X',
y:
Y')
satisfies X X' and Y
Y'. If there is no most specific applicable definition of
or if there is more than one most specific applicable definition of ,
then an error occurs when the expression a b
is evaluated.
One of the operands of an operator expression inside a class C can be a super
expression, in which case the set of applicable definitions of the operator is restricted to those defined to take a parameter
of type T in that position, where T is any proper ancestor of C. This way a method overriding
an operator can invoke its superclass’s definition of that operator.
To accommodate super
expressions, the grammar rules for expressions would be changed as described below.
The SuperExpression grammar rule is split into two to make FullSuperExpression into a separate rule:
super
, which may only be used inside a class C, can be applied to a subexpression that evaluates
to an instance v of C. That subexpression can be either a ParenExpression
or omitted, in which case it defaults to this
.
As specified in the grammar below, the SuperExpression must be embedded as an operand of one of the following operators:
.
(property lookup), []
(indexing), ()
(call), new
,
or new()
operator (for the ()
and new()
operators the SuperExpression
must be a FullSuperExpression)in
operator+
, -
, ~
, ++
, or --
operator+
, -
, *
, /
, %
, <<
,
>>
, >>>
, |
, ^
, &
, <
,
>
, <=
, >=
, ==
, !=
, ===
, or !==
operator+=
, -=
, *=
, /=
, %=
, <<=
,
>>=
, >>>=
, |=
, ^=
, or &=
operatorsuper
changes the behavior of the operator in which it is embedded by limiting its property search to definitions
inherited from class C’s superclass. See property lookup and
operator dispatch.
These grammar rules are modified to permit super
expressions as operands of ()
(call), new
,
new()
, and postfix ++
and --
. Unmodified rules are not shown below.
++
--
These grammar rules are modified to permit super
expressions as operands of unary +
, -
,
and ~
as well as prefix ++
and --
operators.
delete
PostfixExpressionvoid
UnaryExpressiontypeof
UnaryExpression-
NegatedMinLong!
UnaryExpressionThese grammar rules are modified to permit super
expressions as operands of binary *
, /
,
and %
operators.
These grammar rules are modified to permit super
expressions as operands of binary +
and -
operators.
These grammar rules are modified to permit super
expressions as operands of the <<
, >>
,
and >>>
operators.
These grammar rules are modified to permit super
expressions as operands of the <
, >
,
<=
, and >=
operators and the right operand of an in
operator.
These grammar rules are modified to permit super
expressions as operands of the ==
, !=
,
===
, and !==
operators.
These grammar rules are modified to permit super
expressions as operands of the &
, ^
,
and |
operators.
These grammar rules are modified to permit super
expressions as operands of the +=
, -=
,
*=
, /=
, %=
, <<=
, >>=
, >>>=
,
|=
, ^=
, and &=
operators.
All operators have built-in definitions. These are summarized in the table below.
Name | Signature | Behavior |
---|---|---|
"()" |
( x:Object, ... args) |
Call x with the arguments args and return the result. |
"new" |
( x:Object, ... args) |
Call x’s constructor slot with the arguments args and return the result. |
"[]" |
( x:Object, n:Object) |
Return the value of x’s most derived public property named n.toString() .
See property lookup. |
"[]=" |
( x:Object, y:Object, n:Object) |
Set the value of x’s most derived public property named n.toString()
to y. |
( x:Array, y:Object, n:Object) |
Set the value of x’s most derived public property named n.toString()
to y. Update the length property as in ECMAScript 3. |
|
"delete[]" |
( x:Object, n:Object) |
Try to delete x’s most derived public property named n.toString() . |
"~" |
( x:Object) |
Return the bitwise complement of ToInt32(operator "+"( x) ). |
"++" |
( x:Object) |
Return operator "+"( operator "+"( x), 1) . |
"--" |
( x:Object) |
Return operator "-"( operator "+"( x), 1) . |
"+" |
( x:Object) |
Return ToNumber(x). |
( x:Object, y:Object) |
Let x' = ToPrimitive(x) and y' = ToPrimitive(y).
If at least one of x' and y' is a string, then return operator "+"( x', y') .
Otherwise return ToNumber(x') + ToNumber(y'). |
|
( x:String, y:Object) |
Return x concatenated with y.toString() . |
|
( x:Object, y:String) |
Return x.toString() concatenated with y. |
|
( x:String, y:String) |
Return x concatenated with y. | |
( x:Number, y:Number) |
Return x + y. | |
"-" |
( x:Object) |
Return -ToNumber(x). |
( x:Object, y:Object) |
Return ToNumber(x) – ToNumber(y). | |
"*" |
( x:Object, y:Object) |
Return ToNumber(x) ToNumber(y). |
"/" |
( x:Object, y:Object) |
Return ToNumber(x) / ToNumber(y). |
"%" |
( x:Object, y:Object) |
Return ToNumber(x) % ToNumber(y). |
"<<" |
( x:Object, y:Object) |
Return ToNumber(x) << ToNumber(y). |
">>" |
( x:Object, y:Object) |
Return ToNumber(x) >> ToNumber(y). |
">>>" |
( x:Object, y:Object) |
Return ToNumber(x) >>> ToNumber(y). |
"<" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
">=" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
"in" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
"==" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
"===" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
"&" |
( x:Object, y:Object) |
Return ToNumber(x) & ToNumber(y). |
"^" |
( x:Object, y:Object) |
Return ToNumber(x) ^ ToNumber(y). |
"|" |
( x:Object, y:Object) |
Return ToNumber(x) | ToNumber(y). |
An operator can be overridden only inside a class C. That class should define a function with the operator
attribute (operator
is an additional member modifier attribute).
The function’s name must be one of the strings in the table below. The name must be a string rather than an identifier;
the FunctionName grammar rule is extended to allow strings:
A FunctionName can be a String only for operator overrides.
The table also defines the acceptable signatures that the operator can have. At least one parameter must be specified as
taking a value of type C (a type that is a subclass of C is not acceptable). If the operator is ===
,
then both parameters must take values of type C. A class can define several different signatures of the same binary
(but not unary) operator, differing in the choice of parameter types X or Y, which may be omitted if
they are Object
. The parameters denoted as taking types C, X, or Y in the table
below cannot be optional or named parameters. The return type of an operator override can be any type.
Name | Signatures |
---|---|
"~" |
( x: C) |
"+" |
( x: C) ( x: C, y: Y) ( x: X, y: C) ( x: C, y: C)
|
"*" |
( x: C, y: Y) ( x: X, y: C) ( x: C, y: C)
|
"===" |
( x: C, y: C) |
"in" |
( x: X, y: C) |
"()" |
( x: C, a1, ..., an) |
"[]=" |
( x: C, y: Y, a1, ..., an) |
The operator methods should return the result of the operator. For the ++
and --
operators,
the result is the incremented or decremented value. The ()
, new
, []
, delete[]
,
and []=
operators take additional argument lists. If desired, these argument lists can include optional, named,
or rest arguments. The []=
operator must take one more initial required parameter, which is the value stored
in the element.
The >
, >=
, !=
, and !==
operators cannot be overridden directly
because they are syntactic sugars for invocations of the <
, <=
, ==
, and ===
operators; override the latter operators instead. The !
, ||
, ^^
, &&
,
and ?:
operators cannot be overridden directly, but they are affected by any redefinition 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)
Definitions of operators cannot specify a namespace. No access restrictions can be imposed on operators.
The following class illustrates overriding a few operators.
class Complex { var x:Number, y:Number; operator function "+"(a:Complex):Complex { return a; } operator function "-"(a:Complex):Complex { return new Complex(x: -a.x, y: -a.y); } operator function "+"(a:Complex, b:Complex):Complex { return new Complex(x: a.x+b.x, y: a.y+b.y); } operator function "+"(a:Complex, b:Number):Complex { return new Complex(x: a.x+b, y: a.y); } operator function "+"(a:Number, b:Complex):Complex { return new Complex(x: a+b.x, y: b.y); } function toBoolean():Boolean { return this.x != 0 || this.y != 0; } }
toBoolean
MethodJust like toString
, every object has a toBoolean
method that can be overridden. This method is
called by statements such as if
, while
, do while
, and for
and operators
such as !
, ||
, ^^
, &&
, and ? :
. This method
should return either true
or false
.
An object x’s class can override the meaning of for-in loops such as for (
v in
x)
by overriding the following three methods which are located in the system-defined namespace Iterator
:
Method | Description |
---|---|
x.Iterator::forIn() |
If the iteration is empty, return null . Otherwise, return an object o with public
properties named value and state (o may also have other properties). The value
of o.value is the first element returned by the iteration. The value of o.state
can have any type and will be passed exactly once to either Iterator::next or Iterator::done .
The value of o is not guaranteed to be valid after o.state is passed to Iterator::next
or Iterator::done . |
x.Iterator::next( i) |
i was returned by a previous call to Iterator::forIn or Iterator::next .
If the iteration is done, return null . Otherwise, return an object o with public
properties named value and state (o may also have other properties). The value
of o.value is the next element returned by the iteration. The value of o.state
can have any type and will be passed exactly once to either Iterator::next or Iterator::done .
The value of o is not guaranteed to be valid after o.state is passed to Iterator::next
or Iterator::done . |
x.Iterator::done( i) |
i was returned by a previous call to Iterator::forIn or Iterator::next .
This call alerts x that the iteration is exiting prematurely and it can do whatever cleanup it needs on i.
Every i returned by Iterator::forIn or Iterator::next is guaranteed to be subsequently
passed exactly once either to Iterator::next or Iterator::done . Iterator::done
should return undefined . |
A few other approaches had been considered for overriding the for-in operator. The three-method approach above was chosen for the following reasons:
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 ECMAScript 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 Thursday, May 22, 2003 |