これを書いたら欲が出て来たので。

ちなみに「プロになるためのJavaScript入門」は参考書にした一冊。この場を借りて献本御礼。

無限リスト

自然数を受け取って対応する値を返す関数を一つ食わせるだけです。

var ll = List.Lazy(function(i){return i}); // also predefined as List.Integers
p( ll.length  )     // Inifity
p( ll.get(42) )     // 42
p( ll.take(42) )    // [0..41]
p( ll.map(function(x){ return x * x })
    .filter(function(x){ return x % 2 == 1 })
    .take(10) );    // 1,9,25,49,81,121,169,225,289,361
p( ll.map(function(x){ return x * x })
    .filter(function(x){ return x % 2 == 1 })
    .map(function(x){ return Math.sqrt(x) })
    .take(10) );    // 1,3,5,7,9,11,13,15,17,19
try {
    p(ll.toArray());
}catch(e){
    p(e)
}

注意点

以下は一見問題なさそうですが、無限ループになります。

List.Integers
    .map(function(x){ return x*x })
    .filter(function(x){ return x % 2 === 1 })
    .filter(function(x){ return x < 100 })
    .take(10)     // TAKES FOREVER

何が問題かというと、最後の.take(10)。これは要素10個がたまるかリストが尽きるまで要素を集めるのですが、要素が5つしかないためこうなります。Ruby 2.0でも同様です。

(1..Float::INFINITY).lazy.select{|x| x < 5}.take(10).force

無限FizzBuzz

var fizzbuzz = List.Lazy(function(n) {
    if (n % 15 === 0) return 'FizzBuzz';
    if (n %  5 === 0) return 'Buzz';
    if (n %  3 === 0) return 'Fizz';
    return n;
}); 
p( fizzbuzz.length  );
p( JSON.stringify(fizzbuzz.take(30), null, 2) );
fizzbuzz = List.Integers
    .map(function(x, i){ return i %  3 !== 0 ? x : 'Fizz'})
    .map(function(x, i){ return i %  5 !== 0 ? x : 'Buzz'})
    .map(function(x, i){ return i % 15 !== 0 ? x : 'FizzBuzz'})
p( JSON.stringify(fizzbuzz.take(30), null, 2) );

有限リスト

関数を一個渡す代わりに、getプロパティとlengthプロパティの入ったオブジェクトを以下のように渡すことで、有限リストも実現できます。

// or use List.xrange which is more convenient
var ll = List.Lazy({
    get:function(i){return i},
    length:1e3
}); 
p( ll.length  )     // 1000
p( ll.get(42) )     // 42
p( ll.take(42) )    // [0..41]
p( ll.map(function(x){ return x * x })
    .filter(function(x){ return x % 2 == 1 })
    .take(10) );    // 1,9,25,49,81,121,169,225,289,361
p( ll.map(function(x){ return x * x })
    .filter(function(x){ return x % 2 == 1 })
    .map(function(x){ return Math.sqrt(x) })
    .take(10) );    // 1,3,5,7,9,11,13,15,17,19
/* 有限リストの場合、これも問題ない */
p( ll.map(function(x){ return x * x })
    .filter(function(x){ return x % 2 == 1 })
    .map(function(x){ return Math.sqrt(x) })
    .filter(function(x){ return x < 10 })
    .take(10) );    // 1,3,5,7,9
/* toArray() もできる */
p( ll.map(function(x){ return x * x })
    .filter(function(x){ return x % 2 == 1 })
    .map(function(x){ return Math.sqrt(x) })
    .filter(function(x){ return x < 10 })
    .toArray() );   // 1,3,5,7,9

List.xrange

もっともこういった場合は、むしろこうしたくなるでしょう。pythonのxrangeと同じです。List.rangeもあります。(やってることは xrange(…).toArray() )

var xrange = List.xrange,
    range  = List.range;
p( xrange(42).length );
p( xrange(42).get(22) );
p( xrange(42).take(10) );
p( xrange(42).toArray() );
p( range(10) );
p( range(1,11) );
p( range(0,30,5) );
p( range(0,10,3) );
p( range(0,-10,-1) );
p( JSON.stringify(range(0)) );
p( JSON.stringify(range(1,0)) );

工夫のしどころ

Ruby 2.0のEnumerable::Lazyとは違ったやり方をしています。

Ruby2.0では、mapやselectを「積んで」おき、forceのタイミングでそれを順繰りに適用しています。

irb(main):001:0> (1..Float::INFINITY).lazy
=> #<Enumerator::Lazy: 1..Infinity>
irb(main):002:0> (1..Float::INFINITY).lazy.map{|x| x*x}
=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>
irb(main):003:0> (1..Float::INFINITY).lazy.map{|x| x*x}.select{|x| x%2==1}
=> #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>:select>
irb(main):004:0> (1..Float::INFINITY).lazy.map{|x| x*x}.select{|x| x%2==1}.take(10)
=> #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>:select>:take(10)>
irb(main):005:0> (1..Float::INFINITY).lazy.map{|x| x*x}.select{|x| x%2==1}.take(10).force
=> [1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
irb(main):006:0> 

これは以前 Proof of Concept として書いた、

と同様の手法です。

これに対して、List.Lazyではmapやfilterを関数合成で実現しています。これだとfilterをどうするかという問題が生じるわけですが、HaskellのMaybeモナドと同様の手法で解決しています。filterがrejectしたものはNothing相当のものを返し、ary[n]に相当するlazylist.get(n)ではそれをundefinedに変換し、lazylist.take(n)では集めずに捨てるというやり方です。詳しくはソースを参照のこと。

Enjoy!

Dan the Lazy Programmer

Free Form


list-lazy.js