なぜamachangが「すごい」といっているかおわかりになるだろうか。

IT戦記 - ActionScript 3.0 の勉強会資料 経由 amachang - ActionScript 3.0 勉強会資料

flash.utils.Dictionary

このクラスはすごい
ある意味 ECMAScript の
常識をぶち壊す
神秘のようなもの
ECMAScript もビビる!
俺もビビる
あ、ちょ、痛っ
石投げないで><

以下のJavaScriptを考えてみよう。

var a1 = [];
var a2 = [1,2,3]
var o1 = {};
var o2 = {'one':1, 'two':2, 'three':3};
var dict = {};
dict[a1] = 'a1'; 
dict[a2] = 'a2';
dict[o1] = 'o1';
dict[o2] = 'o2';
var result = ''; 
for (p in dict){
  result += '(' + p + ' = ' + dict[p] + ') ';
}

実行結果は以下のとおりとなる。

オブジェクトのキーとしてオブジェクトを利用する場合、JavaScriptではオブジェクトそのものではなく、object.toString()の値がキーになってしまうので、こういう結果になる。

このことは、Perl 5でも同様なのだが、少しはましに見える。

#!/usr/local/bin/perl
use strict;
use warnings;
use CGI;
my $a0 = [];
my $a1 = [1,2,3];
my $h0 = {};
my $h1 = {one=>1,two=>2,three=>3};
my $q  = CGI->new();
my %hash;
$hash{$a0} = 'a0';
$hash{$a1} = 'a1';
$hash{$h0} = 'h0';
$hash{$h1} = 'h1';
$hash{$q}  = 'q';
print "$_ => $hash{$_}\n" for keys (%hash);

実行結果:

ARRAY(0x180127c) => a1
ARRAY(0x1801180) => a0
HASH(0x182c4d8) => h0
HASH(0x182c460) => h1
CGI=HASH(0x182c430) => q

このように、同じオブジェクトの文字列化でも、Perlの場合IDに相当する情報が取れるので、そのままでもある程度使える。

しかし、このようにすると馬脚ならぬ駝脚が現れる。

#最後のprintを以下のとおり変更
print "ref($_) = ", ref $_, "\n"  for keys (%hash);

実行結果:

ref(ARRAY(0x180127c)) = 
ref(ARRAY(0x1801180)) = 
ref(HASH(0x182c4d8)) = 
ref(HASH(0x182c460)) = 
ref(CGI=HASH(0x182c430)) = 

このとおり、ハッシュキーはあくまでもオブジェクトを文字列化したものであって、オブジェクトそのものではない。

ところが、Tie::RefHashを使うだけで、この壁は簡単に乗り越えられる。

use Tie::RefHash;
# ...
tie my %hash, 'Tie::RefHash';
# ...

実行結果:

ARRAY(0x180127c) => a1
ARRAY(0x1801180) => a0
HASH(0x1835bb0) => h1
HASH(0x1835c28) => h0
CGI=HASH(0x1835b80) => q
ref(ARRAY(0x180127c)) = ARRAY
ref(ARRAY(0x1801180)) = ARRAY
ref(HASH(0x1835bb0)) = HASH
ref(HASH(0x1835c28)) = HASH
ref(CGI=HASH(0x1835b80)) = CGI

残念ながら、Pure JavaScript でこれと同様のことをやるのは難しそうだ。というのも、JavaScriptでは同じobjectかどうかを確認する===演算子はあっても、ObjectのIDを取得する方法が現時点では見当たらないからだ。無理矢理こさえると、こんなところになるだろうか。

function RefHash(){
   this.keys   = [];
   this.values = [];
   this.fetch = function(key){
     for (var i = 0, l = this.keys.length; i < l; i++){
       if (this.keys[i] === key) return this.values[i];
     }
     return null;
   };
   this.store = function(key, value){
     var i;
     var l = this.keys.length;
     for (i = 0; i < l; i++){
       if (this.keys[i] === key) break;
     }
     this.keys[i]   = key;
     this.values[i] = value;
     return this.keys.length - 1;
   }
}
var a1 = [];
var a2 = [1,2,3]
var o1 = {};
var o2 = {'one':1, 'two':2, 'three':3};
var dict = new RefHash();
dict.store(a1, 'a1'); 
dict.store(a2, 'a2');
dict.store(o1, 'o1');
dict.store(o2, 'o2');
dict.store(a2, 'A2'); // 上書きのテスト
var result = ''; 
for (var i = 0, l = dict.keys.length; i < l; i++){
  var p = dict.keys[i];
  result += '(' + p + ' = ' + dict.fetch(p) + ') ';
}

実行結果:

見ての通り、リニアサーチなので遅いこときわまりない。Perlの文字列化とか数値化(文字列化した場合の括弧の中身、すなわちアドレス)や、Rubyのobject.object_idのようなオブジェクトIDはJavaScriptの場合取れないのだろうか....

Dan the RefHasher