遅延リスト

配列をイテレータでまわして、とやっていると遅延評価して欲しいなーって時があるよね。webmonkey 風文体。そうでもない。詳しいことはわからないのですが、適当に書いてみました。mala が何年前に通った道ですか?いやもうそういうのはいいや。このごろ別にスーパーじゃなくていい気がしてきたよ。
protptype.js の多分 1.4.0 以上に依存。

LazyList = Class.create();
LazyList.prototype = {
    initialize: function(_head, tail) {
        this._head = _head;
        this.tail = tail;
    },
    head: function() {
        return this._head;
    },
    _each: function(iterator) {
        var list = this;
        while(!list.isEmpty()) {
            iterator(list.head());
            list = list.tail();
        }
    },
    isEmpty: function() {
        return this == $empty;
    },

    // Enumerable のメソッドを避けるため頭に 'l' を付けてます
    lMap: function(iterator) {
        if(this.isEmpty()) return $empty;
        return new LazyList(iterator(this.head()), (function() {
            return this.tail().lMap(iterator);
        }).bind(this));
    },
    lFilter: function(iterator) {
        var list = this;
        while(!list.isEmpty()) {
            if(iterator(list.head())) {
                return new LazyList(list.head(), function() {
                    return list.tail().lFilter(iterator);
                });
            }
            list = list.tail();
        }
        return $empty;
    },
    lTake: function(count) {
        if(count == 0) return $empty;
        return  new LazyList(this.head(), (function() {
            return this.tail().lTake(count - 1);
        }).bind(this));
    }
    // .. こんな調子で続く
};
Object.extend(LazyList.prototype, Enumerable);

var $empty = LazyList.empty = new LazyList();
var $LL = LazyList.create = function() {
    return $A(arguments).reverse().inject($empty, function(list, value) {
        return new LazyList(value, function() { return list; });
    });
};
var $LR = LazyList.createRange = function(start, end, incr) {
    if(end != undefined && start > end) return $empty;
    return new LazyList(start, function() {
        return LazyList.createRange(start + (incr || 1), end, incr);
    });
};

使用例

var list = $LL(1, 4, 5, 2);
list.lMap(function(value) {
    return value * 2;
});                                // [2, 8, 10, 4]

var range = $LR(1);                // [1, 2, 3, ... ]
range.lFilter(function(value) {
    return value % 3 == 0;
}).lTake(5);                       // [3, 6, 9, 12, 15]

これって再帰でスタックがいっぱいになる?よくわからない。