Recently in Core Language Category

最近質問を受けたので見てみたら Flash のヘルプドキュメントが古いのに気づいたので今日はこの話題です。(LiveDocs の AS3 リファレンス は最新です)

さて、まず下の例を考えてみます。

var foo:Boolean = true;
var bar:Object = {};
 
trace(foo && bar);  // 論理積 : 出力は [object Object]
trace(foo || bar);  // 論理和 : 出力は true
 

論理積と論理和の結果は、演算子の左側の式 (この場合は foo) の値で決定されます。ごく単純化すると、ルールは

論理積 (&&) : 左側の式が false なら左側の式の値、true なら右側の式の値
論理和 (||) : 左側の式が true なら左側の式の値、false なら右側の式の値
 

です。つまり、実際に論理演算が行われているわけではないのですね。演算結果の値も Boolean 型になるとは限りません。

さて、AS3 のプログラムでは Boolean もオブジェクトなので、左側の式を評価した後その値の型が何であっても同じルールを適用することができます (されます)。とにかく左辺の式を評価したら、結果を Boolean 型に変換して値を参照します。

それぞれの型を Boolean 型に変換した場合の値は以下のようになります。

前回の続きです。なぜ "" == foo が true と評価されるのか、まず、暗黙の型変換が行われる際のルールを一つ確認します。

暗黙の型変換の基準

暗黙の型変換が行われる際の基準となる型は、変数に関連付けられた型注釈ではなく、値であるオブジェクト自体の型です。ですので変数の型に注目していても暗黙の型変換の結果は分かりません。

例えば、今回問題になっている例 foo:Object = 0 では、値である 0 の型が暗黙の型変換の基準になります。foo の型ではありません。そのため if 文の条件は文字列とと数値の比較として扱われます。

var foo:Object = 0;
if ("" == foo) { // 実際には "" == 0 が評価される
  trace("foo is null String");
}
 

そうすると、今回のケースでは "" と 0 が等価であるかどうかが評価されていることになります。

今回は AS3 の型システムの特徴についての話です。strict モードの人には関係ない話が続きましたが、ここからはコンパイラの環境設定に依存しない話題です(たぶん)。

まず、下のコードについて考えてみます。 オブジェクト型の変数 foo に数値の 0 を代入して空文字列と比較しています。

var foo:Object = 0;
if ("" == foo) {
  trace("foo は空文字列です");
}
 

上の If 文は空文字を検出するためのものに見えますが、実際にはこれを実行すると ”foo は空文字列です” が表示されます。

この結果は予想通りだったでしょうか?想定外だった人はこの先を。

上記の結果は、AS3 が弱い型の性質を持っているために起こります。AS3 では ’+’ 等のオペレータに対して複数のデータ型が混在すると処理の前に暗黙の型変換が行われます。これによって例えば trace("count:" + 0) のように文字列と数値を”足して”もそれらしい結果を得ることができるわけです。

暗黙の型変換は上の trace() の例のようにしばしば便利な機能として使われますが、一方で予期せぬ結果になるケースもある面倒なものだったりもします。例えば、この記事の最初に挙げた例も暗黙の型変換の結果によるものですが、実際にどのような変換が行われて true と評価されたのかは直感的には理解し難いものです。

前回に引き続き変数宣言の有無による違いを少し追求してみたいと思います。今回はパフォーマンスの違いについてです。よく知られている話だとは思いますがいちおう。

変数宣言の有無とパフォーマンス

まずは、以下のコードを考えます。Date のオブジェクトを生成して getTime() メソッドを 10 万回呼び出しています。変数宣言は行われていません。

date = new Date()
for (i=0; i<100000; i++)
  date.getTime()
 

手持ちの環境で上のコードを実行してみます。すると、for ループの箇所の実行に約 470ms かかりました。

これを下のように変えてみます。2 行目で var を使った変数宣言を追加しています。それから for ループ内では、2 行目で新しく追加した変数経由でメソッドを呼び出しています。

date = new Date()
var dateVar = date  // 追加
for (i=0; i<100000; i++)
  dateVar.getTime()
 

こちらはループの実行に約 340ms かかりました。処理時間が 130ms 程短縮されています。

ちょっと前の記事 (AS3 で動的言語のススメ) に書いたように strict オプションを指定しないことで AS3 の記述の自由度は格段に上がります。とはいえ、”With great power comes great responsibility” の言葉どおり、選択肢が増えた分だけ、コードを記述する側の責任が大きくなることは意識しておく必要があります。

手抜きの書き方を勧めたことに少し責任を感じて、今回は var による変数宣言の有無による変数の振舞いの違いに少し触れてみたいと思います。

var 無し変数の初期化

Flash CS3 でも Flex でも AS3 コンパイラの厳密な型チェックのオプションをオフにすれば var 宣言無しで変数が使用できるようになります。例えば下のコードは問題なくコンパイルできます。

public class Foo
{
  public function setMyGlobal(val:int):void
  {
    i = val;
  }
  public function printMyGlobal():void
  {
    trace(i);
  }
}
 

継承の話題の続きです。

インスタンスメソッドのオーバーライド

継承したクラスのインスタンスメソッドをオーバーライドするには override キーワードを使います。オーバーライドするメソッドは以下の条件を満たす必要があります。

  • 同じ名前である
  • 同じアクセス修飾子である
  • 同じ数の引数を持つ
  • 個々の引数の型が同じである
  • 戻り値の型が同じである

おくればせながら、継承に関する話題を少しまとめます。

extends

サブクラスを定義するには extends キーワードを使います。

// 親クラスの定義
public class MyBase 
{
  public function hello():String {
    return "hello";
  }
}
// サブクラスの定義
public class MySub extends MyBase
{
}

NaN と Infinity

Number 型は浮動小数点数以外に3つの値を持ちます。

最初の2つは Infinity と -Infinity です。Infinity は正の無限大の意味で Number.POSITIVE_INFINITY として定義されています。Number.MAX_VALUE の範囲を超えた値はこれになります。同様に-Infinity は負の無限大の意味で Number.NEGATIVE_INFINITY として定義されています。こっちは -Number.MAX_VALUE よりも小さい値ということになります。

3つ目の値は NaN で、Number ではない値という値です。Number.NaN でも使えます。

これら3つの値は、それぞれ以下のような場合に使われます。

Number (livedocs@lab) は整数や浮動小数点数を表すことのできる型です。Number のオブジェクトは IEEE-754 に定義される 64-bit の倍精度のフォーマットで約 4.94e-324 から 1.80e+308 の値を正負それぞれの領域で表すことができます。このため int や uint ではカバーできない数値も扱うことができます。

正確な最大値/最小値は以下のとおり定義されています。

Number.MAX_VALUE // 正の数の最大値
Number.MIN_VALUE // 正の数の最小値

Number オブジェクトは toString() メソッドを使って文字列に変換できます。Number クラスでも引数に基数を指定することができます。が、指定した場合は小数部が無視されます。

int 型と uint 型

int (livedocs@lab) と uint (livedocs@lab) はそれぞれ 32 ビットの符号付/符号無し整数を表す型です。

表現できる範囲は int が -2,147,483,648 から 2,147,483,647、uint が 0 から 4,294,967,295 です。この範囲で足りない場合には Number を使うことになります。int/uint を使用したほうが Number よりも演算速度は速いため int/uint で用が足りる場合はこちらを選ぶのがよさそうです。

さて、int/uint それぞれの最大値と最小値は以下のように定義されています。