以下のようにした理由は、実はJavaScriptのArrayが配列ではないことに起因します。
404 Blog Not Found:Algorithm - Ruby 2.0 や Haskell の遅延リストを JavaScript でこれに対して、List.Lazyではmapやfilterを関数合成で実現しています。
JavaScriptのArrayが、(整数個のデータ構造が順序よくならんだ古典的な意味での)配列でないことは、以下のように確認できます。
JavaScript
var ary = new Array(8);
ary[7] = true;
for (var i = 0; i < ary.length; i++) {
var v = ary[i];
p('for(;;): ary[' + i + '] = ' + v);
}
ary.forEach(function(v, i){
p('forEach: ary[' + i + '] = ' + v);
});
p( ary );
これ、mapやforEachを多用する方は一度ははまったのではないでしょうか。
なぜforループでは「全ての要素」にアクセスできるのに、同様のことが出来るはずのforEachでは値が代入された要素にしかアクセスできないのでしょう?
PerlやRubyの配列に慣れていれば、なおのことWTFでしょう。
Perl 5
use v5.14;
use Data::Dumper;
my @ary;
$ary[7] = 1;
for(my $i = 0; $i < @ary; $i++) {
my $v = $ary[$i];
say 'for(;;):$ary[' . $i . '] = ' . $v;
}
while(my ($i, $v) = each @ary) { # perl 5.12 or better
say 'each: $ary[' . $i . '] = ' . $v;
}
map {
my $v = $ary[$_];
say 'map: $ary[' . $_ . '] = ' . $v
} (0 .. @ary - 1);
say Dumper(\@ary);
Ruby 1.9
ary = Array.new
ary[7] = true
i = 0
while i < ary.length
v = ary[i]
puts 'while ... end: ary[' + i.to_s + '] = ' + v.to_s
i += 1
end
ary.each_with_index do |v, i|
puts 'each_with_index:ary[' + i.to_s + '] = ' + v.to_s
end
p ary
もっとも Pythonistas からすれば、PerlやRubyの配列は動的すぎていかがなものかというものかもしれませんが。空リストにいきなりランダムアクセスするというのは、私がPython使うときに二番目によくはまる落とし穴だったりします。
Python 3
lst = []
try:
lst[42] = 'Answer' # raises IndexError; list does not auto-expand
except Exception as e:
print(type(e))
print(e.args)
print(e)
lst = [None] * 8 # explicitly create a 8-element 'uninitialized' list
lst[7] = True
for i in range(len(lst)):
v = lst[i]
print('for in range():lst[' + str(i) + '] = ' + str(v))
[
print('[print() for ]:lst[' + str(i) + '] = ' + str(lst[i]))
for i in range(len(lst))
]
print(lst)
賢明なる読者はもう答えをご存知でしょう。JavaScriptのArrayは配列ではなく配列のふりをした連想配列だからです。
それゆえ、こんなコードが動いてしまいます。
var ary = []; ary[1/0] = 1/0; ary[1/2] = 1/2; p( ary[Infinity] ); p( ary[0.5] ); p( ary.length );
で、実は連想配列に配列をやらせる言語は昨今増えています。有名どころではLuaとPHPがそうですね。
Lua 5
local tab = {}
tab[8] = true
for i = 1,8 do
print('for i = 1,8: tab[' .. i .. '] = ', tab[i])
end
for i, v in pairs(tab) do
print('for in pairs():tab[' .. i .. '] = ', v)
end
function _dump(o) -- http://www.luafaq.org/#T1.15
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. _dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
print(_dump(tab))
PHP 5
<?
$ary = array();
$ary[7] = True;
for($i = 0; $i < 8; $i++) {
$v = $ary[$i];
echo 'for(;;):$ary[' . $i . '] = ' . $v . "\n";
}
foreach($ary as $i => $v) {
echo 'foreach:$ary[' . $i . '] = ' . $v . "\n";
}
var_dump($ary);
?>
こういってしまうのも何ですが、なぜ連想配列が配列の代わりに使われるようになったかといえば、連想配列は配列を兼ねるから。配列を「整数個のデータが並んだもの」と見なすかわりに、「整数というデータに対応するデータをひもづけたデータ構造」とみなせば、整数以外のデータも「見出し」に使えるようにしたものが連想配列だと言えます。連想配列の方がより一般的なわけです。
「なんなら連想配列だけでいいじゃん」となるのは自然なことですが、実はこれでも一つだけ重要な非互換が残ります。
それは、連想配列には「キーに対応する値が不在」という状態が存在すること。
配列の場合には、こういった状態はありえません。動的配列の場合、長さ0の配列の99番目(0番目から数えています)に値を代入すると、まず配列の長さが100になった上で、残りの99は未定義の値で埋められた状態になります(たいていの場合undefやnilだが、C/C++のように本当に初期化されていない場合も少なくない)。あくまで残りの99の値は未定義なのであって、不在ではないのです。
ところが、連想配列の場合は、不在という状態がいくらでもありえます。whatever[k] === undefinedとなっていも、これはkというキーに undefined が代入されただけなのか、それともそもそもkというキーが不在なのかははっきりしないのです。
そのため、まともな連想配列の実装には、必ずキーの存在確認のための手段が用意されています。Perlならexists(%h, k)、Ruby なら h.key?(k) そしてJavaScriptやPythonなら k in o といった具合に。
ところが、連想ぬきの配列との触れ込みで使われているデータ構造に対して、添字が範囲内に収まっているかを気にするプログラマーは少なくなくとも、不在を気にかけるプログラマーはそれほどいないわけです。
もっとも不在なキーを普通に添字で読もうとすると例外ぶんなげる Python にとってはむしろ常識なのかも知れませんが。実はPython使ってる時に一番はまることが多いのがこれ。書き込み時ではなく読み込み時のみってのがくせ者です。
dct = {}
try:
dct[7] = True
print('7 in dct:', 7 in dct)
print('0 in dct:', 0 in dct)
print('dct.get(0):' + str(dct.get(0)))
print('dct[0]:' + dct[0]) # raises KeyError
except Exception as e:
print(type(e))
print(e.args)
print(e)
print(dct)
ここで、JavaScriptの例に立ち戻ってみます。 ary[Infinity] が成立するということは…
連想配列って、無限配列としても見なせるってこと?
だとしたら、JavaScriptの「配列」に対応する無限リストは、やはり無限配列としてもふるまって欲しい。そんなわけで、List.Lazyの実装はああなったのでした。.get(k)では必ず(不在でもundefined)値を返す代わりに、.has(k)でキーkの存在が確認できる。それを実現するためには、「未定義」と「不在」をきちんと峻別できなければならない…
とにもかくにも、連想配列には不在という状態が存在し、それを忘れてEnumerableなつもりでイテレーターをかけると「どうしてこうなった」が多発するというお話でした。
Dan the Man with too Many Kinds of Arrays to Harness

このブログにコメントするにはログインが必要です。
さんログアウト
この記事には許可ユーザしかコメントができません。