以下のようにした理由は、実は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は未定義の値で埋められた状態になります(たいていの場合undefnilだが、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