以下のようにした理由は、実は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
このブログにコメントするにはログインが必要です。
さんログアウト
この記事には許可ユーザしかコメントができません。