JavaScript 2.0
根拠
演算子
previousupnext

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++ ( = xx = operator "++"(x), ) ただし は一時変数
--x (x = operator "--"(x))
x-- ( = xx = operator "--"(x), ) ただし は一時変数
x(a1, ...an) operator "()"(xa1, ...an)
new x operator "new"(x)
new x(a1, ...an) operator "new"(xa1, ...an)
x[a1, ...an] operator "[]"(xa1, ...an)
x[a1, ...an] = y operator "[]="(xya1, ...an)
delete x[a1, ...an]   operator "delete[]"(xa1, ...an)
x + y operator "+"(xy)
x - y operator "-"(xy)
x * y operator "*"(xy)
x / y operator "/"(xy)
x % y operator "%"(xy)
x << y operator "<<"(xy)
x >> y operator ">>"(xy)
x >>> y operator ">>>"(xy)
x < y operator "<"(xy)
x > y operator "<"(yx)
x <= y operator "<="(xy)
x >= y operator "<="(yx)
x in y operator "in"(xy)
x == y operator "=="(xy)
x != y !(operator "=="(xy))
x === y operator "==="(xy)
x !== y !(operator "==="(xy))
x & y operator "&"(xy)
x ^ y operator "^"(xy)
x | y operator "|"(xy)
x += y (x = operator "+"(xy))
x -= y (x = operator "-"(xy))
x *= y (x = operator "*"(xy))
x /= y (x = operator "/"(xy))
x %= y (x = operator "%"(xy))
x <<= y (x = operator "<<"(xy))
x >>= y (x = operator ">>"(xy))
x >>>= y (x = operator ">>>"(xy))
x &= y (x = operator "&"(xy))
x ^= y (x = operator "^"(xy))
x |= y (x = operator "|"(xy))

演算子のディスパッチ

単項演算子では普通のメソッドと同じように単一ディスパッチが行われる。in は単項演算子と同じように右オペランドのクラスでのみオーバーライドされ、右オペランドにのみディスパッチされる。null は通常のメソッドディスパッチと同様に NullObject のみのメンバとみなされる。

二項演算子では両オペランドの型に基づいて二重ディスパッチが行われる。二項演算子 a  b という式で呼び出されると、適用可能な全ての operator ""(xy) メソッドが探索され、最適なものが呼び出される。operator ""(x:Xy:Y) の定義が a  b に対して適用可能であるとは a の値が Xメンバであり、且つ b の値が Yメンバである場合である。ただし、普通のメソッドのディスパッチと同様に null は型 Null 及び Object のみのメンバとみなされ対象外となる。operator ""(x:Xy:Y) の定義が最適であるとはその演算子が適用可能であり、且つ適用可能な全ての operator ""(x:X'y:Y') の定義が X X' 且つ Y を満たす場合である。 の最適な定義が1つも見つからなかったり、複数存在する場合は式 a  b の評価時にエラーになる。

クラス C 内の演算子のオペランドでは superを使うことができる。この場合その演算子の適用可能な定義はその位置で型 T の引数をとるように定義するものに限定される。ここに T はクラス C の適当な祖先クラスである。これにより演算子をオーバーライドするメソッドはその演算子の基底クラスでの定義を呼び出すことができる。

式文法の変更

super 式に対応するために、式に関する文法規則は以下のように変更される。

super 式

FullSuperExpression を別の規則に分離するために、文法規則 SuperExpression は2つに分割される:

SuperExpression 
   super
|  FullSuperExpression
FullSuperExpression  super ParenExpression

super はクラス C 内でのみ使用可能であり、C のインスタンス v として評価される部分式に適用可能である。この部分式は ParenExpression か或いは省略することができる。省略した場合はデフォルトで this になる。

以下の文法に示すように、SuperExpression は次のいずれかの演算子のオペランドとして組み込まれなければならない:

super はプロパティ探索をクラス C の基底クラスから継承された定義に限定することで、組み込まれている演算子の挙動を変更する。「プロパティ探索」及び「演算子のディスパッチ」も見よ。

後置式

以下の文法規則は super 式を () (関数呼び出し)、newnew() 、後置 ++ 、及び後置 -- のオペランドとして許容するために修正される。修正の無い規則は以下に挙げていない。

PostfixExpressionOrSuper 
   PostfixExpression
|  SuperExpression
FullPostfixExpression 
   PrimaryExpression
|  ExpressionQualifiedIdentifier
|  FullNewExpression
|  FullPostfixExpression PropertyOperator
|  SuperExpression PropertyOperator
|  FullPostfixExpression Arguments
|  FullSuperExpression Arguments
|  PostfixExpressionOrSuper [no line break] ++
|  PostfixExpressionOrSuper [no line break] --
FullNewExpression 
   new FullNewSubexpression Arguments
|  new FullSuperExpression Arguments
ShortNewExpression 
   new ShortNewSubexpression
|  new SuperExpression

単項演算子

以下の文法規則は super 式を単項 +-~ 、前置 ++ 、及び前置 -- の各演算子のオペランドとして許容するために修正される。

UnaryExpression 
   PostfixExpression
|  delete PostfixExpression
|  void UnaryExpression
|  typeof UnaryExpression
|  ++ PostfixExpressionOrSuper
|  -- PostfixExpressionOrSuper
|  + UnaryExpressionOrSuper
|  - UnaryExpressionOrSuper
|  - NegatedMinLong
|  ~ UnaryExpressionOrSuper
|  ! UnaryExpression
UnaryExpressionOrSuper 
   UnaryExpression
|  SuperExpression

乗算演算子

以下の文法規則は super 式を二項演算子 */ 、及び % のオペランドとして許容するために修正される。

MultiplicativeExpression 
   UnaryExpression
|  MultiplicativeExpressionOrSuper * UnaryExpressionOrSuper
|  MultiplicativeExpressionOrSuper / UnaryExpressionOrSuper
|  MultiplicativeExpressionOrSuper % UnaryExpressionOrSuper
MultiplicativeExpressionOrSuper 
   MultiplicativeExpression
|  SuperExpression

加算演算子

以下の文法規則は super 式を二項演算子 +- のオペランドとして許容するために修正される。

AdditiveExpression 
   MultiplicativeExpression
|  AdditiveExpressionOrSuper + MultiplicativeExpressionOrSuper
|  AdditiveExpressionOrSuper - MultiplicativeExpressionOrSuper
AdditiveExpressionOrSuper 
   AdditiveExpression
|  SuperExpression

ビットシフト演算子

以下の文法規則は super 式を <<>> 、及び >>> 演算子のオペランドとして許容するために修正される。

ShiftExpression 
   AdditiveExpression
|  ShiftExpressionOrSuper << AdditiveExpressionOrSuper
|  ShiftExpressionOrSuper >> AdditiveExpressionOrSuper
|  ShiftExpressionOrSuper >>> AdditiveExpressionOrSuper
ShiftExpressionOrSuper 
   ShiftExpression
|  SuperExpression

関係演算子

以下の文法規則は super 式を <><= 、及び >= 演算子のオペランドとして、また in 演算子の右オペランドとして許容するために修正される。

RelationalExpressionallowIn 
   ShiftExpression
|  RelationalExpressionOrSuperallowIn < ShiftExpressionOrSuper
|  RelationalExpressionOrSuperallowIn > ShiftExpressionOrSuper
|  RelationalExpressionOrSuperallowIn <= ShiftExpressionOrSuper
|  RelationalExpressionOrSuperallowIn >= ShiftExpressionOrSuper
|  RelationalExpressionallowIn is ShiftExpression
|  RelationalExpressionallowIn as ShiftExpression
|  RelationalExpressionallowIn in ShiftExpressionOrSuper
|  RelationalExpressionallowIn instanceof ShiftExpression
RelationalExpressionnoIn 
   ShiftExpression
|  RelationalExpressionOrSupernoIn < ShiftExpressionOrSuper
|  RelationalExpressionOrSupernoIn > ShiftExpressionOrSuper
|  RelationalExpressionOrSupernoIn <= ShiftExpressionOrSuper
|  RelationalExpressionOrSupernoIn >= ShiftExpressionOrSuper
|  RelationalExpressionnoIn is ShiftExpression
|  RelationalExpressionnoIn as ShiftExpression
|  RelationalExpressionnoIn instanceof ShiftExpression
RelationalExpressionOrSuper 
   RelationalExpression
|  SuperExpression

等価演算子

以下の文法規則は super 式を ==!==== 、及び !== 演算子のオペランドとして許容するために修正される。

EqualityExpression 
   RelationalExpression
|  EqualityExpressionOrSuper == RelationalExpressionOrSuper
|  EqualityExpressionOrSuper != RelationalExpressionOrSuper
|  EqualityExpressionOrSuper === RelationalExpressionOrSuper
|  EqualityExpressionOrSuper !== RelationalExpressionOrSuper
EqualityExpressionOrSuper 
   EqualityExpression
|  SuperExpression

ビット演算子

以下の文法規則は super 式を &^ 、及び | 演算子のオペランドとして許容するために修正される。

BitwiseAndExpression 
   EqualityExpression
|  BitwiseAndExpressionOrSuper & EqualityExpressionOrSuper
BitwiseXorExpression 
   BitwiseAndExpression
|  BitwiseXorExpressionOrSuper ^ BitwiseAndExpressionOrSuper
BitwiseOrExpression 
   BitwiseXorExpression
|  BitwiseOrExpressionOrSuper | BitwiseXorExpressionOrSuper
BitwiseAndExpressionOrSuper 
   BitwiseAndExpression
|  SuperExpression
BitwiseXorExpressionOrSuper 
   BitwiseXorExpression
|  SuperExpression
BitwiseOrExpressionOrSuper 
   BitwiseOrExpression
|  SuperExpression

代入演算子

以下の文法規則は super 式を +=-=*=/=%=<<=>>=>>>=|=^= 、及び &= 演算子のオペランドとして許容するために修正される。

AssignmentExpression 
   ConditionalExpression
|  PostfixExpression = AssignmentExpression
|  PostfixExpressionOrSuper CompoundAssignment AssignmentExpression
|  PostfixExpressionOrSuper CompoundAssignment SuperExpression
|  PostfixExpression LogicalAssignment AssignmentExpression

組み込み演算子の定義

全ての演算子は組み込みの定義を持つ。以下の表にその要約を示す。

名前 シグニチャ 動作
"()" (x:Object, ... args) args を引数として x を呼び出し、その結果を返す。
"new" (x:Object, ... args) args を引数として x のコンストラクタスロットを呼び出し、その結果を返す。
"[]" (x:Object, n:Object) xpublic プロパティの内、名前が n.toString() である最終導出プロパティの値を返す。「プロパティ探索」も見よ。
"[]=" (x:Object, y:Object, n:Object) xpublic プロパティの内、名前が n.toString() である最終導出プロパティの値を y に設定にする。
(x:Array, y:Object, n:Object) xpublic プロパティの内、名前が n.toString() である最終導出プロパティの値を y に設定にし、JavaScript 1.5 と同様に length プロパティを更新する。
"delete[]" (x:Object, n:Object) xpublic プロパティの内、名前が 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) xy.toString() を結合したものを返す。
(x:Object, y:String) x.toString()y を結合したものを返す。
(x:String, y:String) xy を結合したものを返す。
(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 
   Identifier
|  get [no line break] Identifier
|  set [no line break] Identifier
|  String

FunctionNameString にできるのは演算子のオーバーライドを行う場合だけである。

表には各演算子が持つことのできるシグニチャも示した。引数の内少なくとも1つ型は C でなければならず (C の派生クラスの型は認められない)、=== 演算子については両方とも型は C でなければならない。1つのクラスは引数の型 XY を変えることで、同じ二項演算子に対して (単項演算子は不可) 異なるシグニチャを定義できる。これらの型が Object である場合は省略できる。以下の表で型が CXY のいずれかである引数は省略可能にしたり名前付きにしたりできない。演算子のオーバーライドを行うメソッドの戻り値の型は何でもよい。

名前 シグニチャ
"~"
"++"
"--"
(x:C)
"+"
"-"
(x:C)
(x:Cy:Y)
(x:Xy:C)
(x:Cy:C)
"*"
"/"
"%"
"<<"
">>"
">>>"
"<"
"<="
"=="
"&"
"^"
"|"
(x:Cy:Y)
(x:Xy:C)
(x:Cy:C)
"===" (x:Cy:C)
"in" (x:Xy:C)
"()"
"new"
"[]"
"delete[]"
(x:Ca1, ...an)
"[]=" (x:Cy:Ya1, ...an)

演算子メソッドはその演算子の結果を返すべきであり、++-- 演算子であれば結果はインクリメント、デクリメントされた値である。()new[]delete[][]= 演算子は余分に引数リストをとり、必要があれば省略可能、名前付き、残余引数を含めることができる。[]= 演算子は1つ以上の省略不可能な引数をとる必要があり、この引数が要素に格納する値となる。

>>=!= 、及び !== 演算子は <<=== 、及び === 演算子呼び出しのシンタックスシュガーになっているため、直接オーバーライドすることはできない (代わりに後者の演算子をオーバーライドするとよい)。!||^^&& 、及び ?: 演算子も直接オーバーライドできないが、これらの演算子は toBoolean の再定義の影響を受ける。||&& 、及び ?: 演算子がオーバーライド不能であるのはこれらが全ての引数を評価するとは限らないからである。プログラマがこの短絡評価を利用するのはよくあることである。! 演算子がオーバーライド不能であるのは以下のような有用な等価性を維持するためである:

演算子の定義に名前空間を指定することはできない。演算子にはいかなるアクセス制限も無い。

以下のクラス定義は演算子のオーバーライドを使った例である。

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 メソッドを持つ。このメソッドは ifwhiledo while 、及び for といった文や、!||^^&& 、及び ? : といった演算子により呼び出される。このメソッドは truefalse のいずれかを返さなければならない。

for-in 演算子

オブジェクト x のクラスはシステム定義の名前空間 Iterator に属する以下の3つのメソッドをオーバーライドすることにより、for (v in x) のようなループの意味をオーバーライドすることができる:

メソッド 説明
x.Iterator::forIn() 反復リストが空であれば null を返し、それ以外のときは名前が valuestate である public プロパティを持つオブジェクト o を返す (o は他にプロパティを持っていてもよい)。o.value の値は反復により最初に返される要素である。o.state の値は型は何でもよく、Iterator::nextIterator::done のいずれかに一度だけ渡される。o.stateIterator::nextIterator::done に渡された後の o の値は正当性を保証されない。
x.Iterator::next(i) i は前回の Iterator::forInIterator::next 呼び出しの戻り値である。反復が終了していればこのメソッドは null を返し、それ以外のときは名前が valuestate である public プロパティを持つオブジェクト o を返す (o は他にプロパティを持っていてもよい)。o.value の値は反復により次に返される要素である。o.state の値は型は何でもよく、Iterator::nextIterator::done のいずれかに一度だけ渡される。o.stateIterator::nextIterator::done に渡された後の o の値は正当性を保証されない。
x.Iterator::done(i) i は前回の Iterator::forInIterator::next 呼び出しの戻り値である。このメソッドの呼び出しは、反復が早めに終了し、必要な解体が可能になったことを x に通知する。Iterator::forInIterator::next から返された各 i は後で Iterator::nextIterator::done のいずれかに一度だけ渡されることが保証される。Iterator::doneundefined を返すべきである。

根拠

for-in 演算子をオーバーライドするためのアプローチは他にもあった。上に挙げた3つのメソッドを使ったアプローチが選ばれたのは以下の理由による:

関数とコールバック (Iterator::forInf をとるようにし、コレクションの各要素について f を呼び出させる) ではなく反復子オブジェクトを使うのは何故か?
これは反復子モデルでは将来的に2つのコレクションで同時に反復を行うような拡張が可能だからである。これはコールバックでは不可能なことである。
Iterator::forInIterator::next が反復子だけを返しその反復子を与えられて要素を返すメソッド Iterator::get を別に用意するようなことをせず、Iterator::forInIterator::next がオブジェクトを返すようになっているのは何故か?
Iterator::get は不可避な同一性の汚染も招く。null (や他の値) はコレクションの要素として正しいものであり、Iterator::get から返された値を使って反復が終了したかどうかを調べることはできない (よって代わりに Iterator::forInIterator::nextnull を返させることによって反復の終了を示さなければならないのである)。しかしながら Iterator::forInIterator::next を呼び出してから Iterator::get を呼び出すまでの間に他のスレッドが最後の要素を削除し、Iterator::get が処理に詰まってしまうこともあり得る。反復の終了に例外を使うこともできるが、プログラム実行パスの早期に例外ハンドリングを行うことになる。
戻り値として2つの要素を持つ読み取り専用の配列ではなく、2つの名前付きスロットを持つオブジェクトを使うのは何故か?
配列の要素は全て同じ型でなければならないが、名前付きスロットはそれぞれ異なる型を持つことができるからである。
Iterator::done を定義したのは何故か?
いくつかのオブジェクトは反復をサポートするために一時的な足場を立てる。このメソッドはそのような足場を解体可能になったことをオブジェクトに知らせる。JavaScript は現在のところ終了処理が欠如しているので、反復が早めに打ち切られたことを反復オブジェクトが知るには他に方法が無い — 同じオブジェクトに対して同時に複数の反復を行うと問題が起こるため、次の反復の開始で前の反復の終了を示すことはできない。

クラスリフレクション演算子

草案の初期のドラフトにはインスタンスのクラスを返す .class 演算子が含まれていた。この演算子は以下の文法生成規則で定義されていた:

PropertyOperator 
   . QualifiedIdentifier
|  . class
|  Brackets

この演算子が削除されたのはインスタンスのクラスの取得が抽象性の破壊を引き起こすからである。ファクトリメソッド [訳注: インスタンスを生成するメソッド] m のクライアントは m が指定されたクラス C 、或いはその派生クラスの内いずれのクラスのインスタンスを返すか決定することが可能になり、m は正確にどのインスタンスを返すかの実装上での決定をクライアントを破綻させずに変更することが不可能になる。


Waldemar Horwat
最終更新: 2003年5月22日 (木)
previousupnext
訳者: exeal <exeal@student.interq.or.jp>
このドキュメントのオリジナルは mozilla.org において英語で公布されています。
この和訳は、利用者の利便のために Mozilla Japan 翻訳部門 によって提供されています。
内容に関してご不明な点がありましたら webmaster までお問い合わせください。