|
JavaScript 2.0
根拠
演算子
|
05/22/2003 (Thr)
オーバーライド可能な演算子は元々 JavaScript 2.0 草案に含まれていたが、JavaScript 2.0 の軽量化のために将来のバージョンに見送られた。実装されるとしたら演算子のオーバーライドは本節で述べるような振る舞いをしていた。
JavaScript 2.0 では + 、* 、a[b]
、-= などのような数多くの演算子が使用可能であり、これらはその実引数の型に基づきディスパッチ (呼び出しの転送)
が行われる。演算子の中には単一のオペランドについてのみディスパッチを行うものもあり、その挙動はプロパティ探索のようなものである。その他の演算子では2つのオペランドに対して同時にディスパッチされる。また、既存の演算子を組み合わせるためのシンタックスシュガーになっているものもある。
JavaScript の全ての演算子は最初から定義済みである。加えてクラスが既存の挙動をオーバーライドすることにより別の定義を提供することもできる。このオーバーライドはディスパッチされるオペランドの少なくとも一方がそのクラスかそのクラスの子孫クラスのインスタンスである場合にのみ適用される。演算子のオーバーライドは有用で、例えば単位を JavaScript 2.0 コア部に追加することなく定義できる。
この節では JavaScript ソースコードにおける演算子の展開、演算子の組み込み定義、及びオーバーライドの意味について示す。
式中で演算子を呼び出すと、以下の表に従って当該演算子が呼び出される。「演算子呼び出し」ではどの演算子が実際に呼び出されるかを示すのに擬似構文を使う (演算子の呼び出しに使用できるのは表の左側の式だけである)。
| 式 | 演算子の呼び出し |
|---|---|
+x |
operator "+"(x) |
-x |
operator "-"(x) |
~x |
operator "~"(x) |
++x |
(x = operator "++"(x)) |
x++ |
( = x, x = operator "++"(x), )
ただし は一時変数 |
--x |
(x = operator "--"(x)) |
x-- |
( = x, x = operator "--"(x), )
ただし は一時変数 |
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)) |
単項演算子では普通のメソッドと同じように単一ディスパッチが行われる。in
は単項演算子と同じように右オペランドのクラスでのみオーバーライドされ、右オペランドにのみディスパッチされる。null
は通常のメソッドディスパッチと同様に Null と Object のみのメンバとみなされる。
二項演算子では両オペランドの型に基づいて二重ディスパッチが行われる。二項演算子
が
a b という式で呼び出されると、適用可能な全ての
operator ""(x, y)
メソッドが探索され、最適なものが呼び出される。operator ""(x:X, y:Y)
の定義が a b
に対して適用可能であるとは a の値が X のメンバであり、且つ
b の値が Y のメンバである場合である。ただし、普通のメソッドのディスパッチと同様に
null は型 Null 及び Object のみのメンバとみなされ対象外となる。operator ""(x:X, y:Y)
の定義が最適であるとはその演算子が適用可能であり、且つ適用可能な全ての operator ""(x:X', y:Y')
の定義が X X' 且つ Y
を満たす場合である。
の最適な定義が1つも見つからなかったり、複数存在する場合は式
a b の評価時にエラーになる。
クラス C 内の演算子のオペランドでは super
式を使うことができる。この場合その演算子の適用可能な定義はその位置で型 T
の引数をとるように定義するものに限定される。ここに T はクラス C
の適当な祖先クラスである。これにより演算子をオーバーライドするメソッドはその演算子の基底クラスでの定義を呼び出すことができる。
super 式に対応するために、式に関する文法規則は以下のように変更される。
FullSuperExpression を別の規則に分離するために、文法規則 SuperExpression は2つに分割される:
super はクラス C 内でのみ使用可能であり、C のインスタンス v
として評価される部分式に適用可能である。この部分式は ParenExpression
か或いは省略することができる。省略した場合はデフォルトで this になる。
以下の文法に示すように、SuperExpression は次のいずれかの演算子のオペランドとして組み込まれなければならない:
. (プロパティ探索)、[] (インデクシング)、() (関数呼び出し)、new
、new() の各演算子の左オペランド (() 、new() 演算子については
SuperExpression は
FullSuperExpression でなければならない)in 演算子の右オペランド+ 、- 、~ 、++ 、-- のオペランド+ 、- 、* 、/ 、% 、<<
、>> 、>>> 、| 、^ 、& 、<
、> 、<= 、>= 、== 、!= 、===
、!== のいずれかのオペランド+= 、-= 、*= 、/= 、%= 、<<=
、>>= 、>>>= 、|= 、^= 、&= のいずれかのオペランドsuper はプロパティ探索をクラス C
の基底クラスから継承された定義に限定することで、組み込まれている演算子の挙動を変更する。「プロパティ探索」及び「演算子のディスパッチ」も見よ。
以下の文法規則は super 式を () (関数呼び出し)、new 、new() 、後置
++ 、及び後置 -- のオペランドとして許容するために修正される。修正の無い規則は以下に挙げていない。
++--以下の文法規則は super 式を単項 + 、- 、~ 、前置 ++
、及び前置 -- の各演算子のオペランドとして許容するために修正される。
delete PostfixExpressionvoid UnaryExpressiontypeof UnaryExpression- NegatedMinLong! UnaryExpression以下の文法規則は super 式を二項演算子 * 、/ 、及び %
のオペランドとして許容するために修正される。
以下の文法規則は super 式を二項演算子 + 、- のオペランドとして許容するために修正される。
以下の文法規則は super 式を << 、>> 、及び >>>
演算子のオペランドとして許容するために修正される。
以下の文法規則は super 式を < 、> 、<= 、及び >=
演算子のオペランドとして、また in 演算子の右オペランドとして許容するために修正される。
以下の文法規則は super 式を == 、!= 、=== 、及び !==
演算子のオペランドとして許容するために修正される。
以下の文法規則は super 式を & 、^ 、及び |
演算子のオペランドとして許容するために修正される。
以下の文法規則は super 式を += 、-= 、*= 、/= 、%=
、<<= 、>>= 、>>>= 、|= 、^= 、及び
&= 演算子のオペランドとして許容するために修正される。
全ての演算子は組み込みの定義を持つ。以下の表にその要約を示す。
| 名前 | シグニチャ | 動作 |
|---|---|---|
"()" |
(x:Object, ... args) |
args を引数として x を呼び出し、その結果を返す。 |
"new" |
(x:Object, ... args) |
args を引数として x のコンストラクタスロットを呼び出し、その結果を返す。 |
"[]" |
(x:Object, n:Object) |
x の public プロパティの内、名前が n.toString()
である最終導出プロパティの値を返す。「プロパティ探索」も見よ。 |
"[]=" |
(x:Object, y:Object, n:Object) |
x の public プロパティの内、名前が n.toString()
である最終導出プロパティの値を y に設定にする。 |
(x:Array, y:Object, n:Object) |
x の public プロパティの内、名前が n.toString()
である最終導出プロパティの値を y に設定にし、JavaScript 1.5 と同様に length プロパティを更新する。 |
|
"delete[]" |
(x:Object, n:Object) |
x の public プロパティの内、名前が n.toString()
である最終導出プロパティの削除を試みる。 |
"~" |
(x:Object) |
ToInt32(operator "+"(x)) のビット反転を返す。 |
"++" |
(x:Object) |
operator "+"(operator "+"(x), 1) を返す。 |
"--" |
(x:Object) |
operator "-"(operator "+"(x), 1) を返す。 |
"+" |
(x:Object) |
ToNumber(x) を返す。 |
(x:Object, y:Object) |
x' = ToPrimitive(x) 、y' = ToPrimitive(y)
とする。x' と y' のいずれかが文字列であれば
operator "+"(x', y') を返す。それ以外の場合は
ToNumber(x') + ToNumber(y') を返す。 |
|
(x:String, y:Object) |
x に y.toString() を結合したものを返す。 |
|
(x:Object, y:String) |
x.toString() に y を結合したものを返す。 |
|
(x:String, y:String) |
x に y を結合したものを返す。 | |
(x:Number, y:Number) |
x + y を返す。 | |
"-" |
(x:Object) |
-ToNumber(x) を返す。 |
(x:Object, y:Object) |
ToNumber(x) – ToNumber(y) を返す。 | |
"*" |
(x:Object, y:Object) |
ToNumber(x) ToNumber(y) を返す。 |
"/" |
(x:Object, y:Object) |
ToNumber(x) / ToNumber(y) を返す。 |
"%" |
(x:Object, y:Object) |
ToNumber(x) % ToNumber(y) を返す。 |
"<<" |
(x:Object, y:Object) |
ToNumber(x) << ToNumber(y) を返す。 |
">>" |
(x:Object, y:Object) |
ToNumber(x) >> ToNumber(y) を返す。 |
">>>" |
(x:Object, y:Object) |
ToNumber(x) >>> ToNumber(y) を返す。 |
"<" |
(x:Object, y:Object) |
JavaScript 1.5 と同じ。 |
"<=" |
(x:Object, y:Object) |
JavaScript 1.5 と同じ。 |
"in" |
(x:Object, y:Object) |
JavaScript 1.5 と同じ。 |
"==" |
(x:Object, y:Object) |
JavaScript 1.5 と同じ。 |
"===" |
(x:Object, y:Object) |
JavaScript 1.5 と同じ。 |
"&" |
(x:Object, y:Object) |
ToNumber(x) & ToNumber(y) を返す。 |
"^" |
(x:Object, y:Object) |
ToNumber(x) ^ ToNumber(y) を返す。 |
"|" |
(x:Object, y:Object) |
ToNumber(x) | ToNumber(y) を返す。 |
演算子のオーバーライドはクラス C 内でのみ可能である。クラスは operator 属性
(operator は演算子オーバーライドのために追加されたメンバ修飾属性)
で関数を定義しなければならず、関数名は以下の表の文字列から選ばなくてはならない。この名前は識別子ではなく文字列でなければならない。FunctionName
文法規則は文字列を許容するように拡張される:
FunctionName を String にできるのは演算子のオーバーライドを行う場合だけである。
表には各演算子が持つことのできるシグニチャも示した。引数の内少なくとも1つ型は C でなければならず
(C の派生クラスの型は認められない)、=== 演算子については両方とも型は C
でなければならない。1つのクラスは引数の型 X 、Y を変えることで、同じ二項演算子に対して (単項演算子は不可)
異なるシグニチャを定義できる。これらの型が Object である場合は省略できる。以下の表で型が C
、X 、Y のいずれかである引数は省略可能にしたり名前付きにしたりできない。演算子のオーバーライドを行うメソッドの戻り値の型は何でもよい。
| 名前 | シグニチャ |
|---|---|
"~" |
(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) |
演算子メソッドはその演算子の結果を返すべきであり、++ 、--
演算子であれば結果はインクリメント、デクリメントされた値である。() 、new 、[]
、delete[] 、[]= 演算子は余分に引数リストをとり、必要があれば省略可能、名前付き、残余引数を含めることができる。[]=
演算子は1つ以上の省略不可能な引数をとる必要があり、この引数が要素に格納する値となる。
> 、>= 、!= 、及び !== 演算子は < 、<=
、== 、及び === 演算子呼び出しのシンタックスシュガーになっているため、直接オーバーライドすることはできない
(代わりに後者の演算子をオーバーライドするとよい)。! 、|| 、^^ 、&&
、及び ?: 演算子も直接オーバーライドできないが、これらの演算子は toBoolean
の再定義の影響を受ける。|| 、&& 、及び ?:
演算子がオーバーライド不能であるのはこれらが全ての引数を評価するとは限らないからである。プログラマがこの短絡評価を利用するのはよくあることである。!
演算子がオーバーライド不能であるのは以下のような有用な等価性を維持するためである:
if (!x) A else B
if (x) B else A!(x && y)
!x || !y != y
!(x == y)演算子の定義に名前空間を指定することはできない。演算子にはいかなるアクセス制限も無い。
以下のクラス定義は演算子のオーバーライドを使った例である。
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 メソッドtoString と同様に、あらゆるオブジェクトはオーバーライド可能な toBoolean メソッドを持つ。このメソッドは
if 、while 、do while 、及び for といった文や、! 、||
、^^ 、&& 、及び ? : といった演算子により呼び出される。このメソッドは
true か false のいずれかを返さなければならない。
オブジェクト x のクラスはシステム定義の名前空間 Iterator
に属する以下の3つのメソッドをオーバーライドすることにより、for (v in x)
のようなループの意味をオーバーライドすることができる:
| メソッド | 説明 |
|---|---|
x.Iterator::forIn() |
反復リストが空であれば null を返し、それ以外のときは名前が value 、state
である public プロパティを持つオブジェクト o を返す (o
は他にプロパティを持っていてもよい)。o.value
の値は反復により最初に返される要素である。o.state の値は型は何でもよく、Iterator::next
か Iterator::done のいずれかに一度だけ渡される。o.state が Iterator::next
か Iterator::done に渡された後の o の値は正当性を保証されない。 |
x.Iterator::next(i) |
i は前回の Iterator::forIn か Iterator::next
呼び出しの戻り値である。反復が終了していればこのメソッドは null を返し、それ以外のときは名前が
value 、state である public プロパティを持つオブジェクト o を返す
(o は他にプロパティを持っていてもよい)。o.value の値は反復により次に返される要素である。o.state
の値は型は何でもよく、Iterator::next か Iterator::done のいずれかに一度だけ渡される。o.state
が Iterator::next か Iterator::done に渡された後の o の値は正当性を保証されない。 |
x.Iterator::done(i) |
i は前回の Iterator::forIn か Iterator::next 呼び出しの戻り値である。このメソッドの呼び出しは、反復が早めに終了し、必要な解体が可能になったことを
x に通知する。Iterator::forIn や Iterator::next から返された各 i は後で Iterator::next か Iterator::done
のいずれかに一度だけ渡されることが保証される。Iterator::done は undefined を返すべきである。 |
for-in 演算子をオーバーライドするためのアプローチは他にもあった。上に挙げた3つのメソッドを使ったアプローチが選ばれたのは以下の理由による:
Iterator::forIn が f をとるようにし、コレクションの各要素について
f を呼び出させる) ではなく反復子オブジェクトを使うのは何故か?Iterator::forIn と Iterator::next が反復子だけを返しその反復子を与えられて要素を返すメソッド
Iterator::get を別に用意するようなことをせず、Iterator::forIn や Iterator::next
がオブジェクトを返すようになっているのは何故か?Iterator::get は不可避な同一性の汚染も招く。null (や他の値)
はコレクションの要素として正しいものであり、Iterator::get から返された値を使って反復が終了したかどうかを調べることはできない
(よって代わりに Iterator::forIn か Iterator::next に null
を返させることによって反復の終了を示さなければならないのである)。しかしながら Iterator::forIn か
Iterator::next を呼び出してから Iterator::get を呼び出すまでの間に他のスレッドが最後の要素を削除し、Iterator::get
が処理に詰まってしまうこともあり得る。反復の終了に例外を使うこともできるが、プログラム実行パスの早期に例外ハンドリングを行うことになる。Iterator::done を定義したのは何故か?草案の初期のドラフトにはインスタンスのクラスを返す .class
演算子が含まれていた。この演算子は以下の文法生成規則で定義されていた:
この演算子が削除されたのはインスタンスのクラスの取得が抽象性の破壊を引き起こすからである。ファクトリメソッド [訳注: インスタンスを生成するメソッド] m のクライアントは m が指定されたクラス C 、或いはその派生クラスの内いずれのクラスのインスタンスを返すか決定することが可能になり、m は正確にどのインスタンスを返すかの実装上での決定をクライアントを破綻させずに変更することが不可能になる。
|
Waldemar Horwat 最終更新: 2003年5月22日 (木) |