ECMA262のIteration protocolsで遅延評価するIteratorを作る
これは何?
ECMA262 6th以降にはiteration protocols(と呼ぶべきもの)が導入されている、というのは皆さんもちろんご存知のとおりだと思います。これを使ってIterator<T>.next()
が呼ばれるまでmap()
などを実行しない(lazy evaluationする)Iteratorを作ってみようという話です。Rustのstd::iter::Iterator
とかC#のLINQとかが似たような挙動をしますよね。
今後、巨大なオブジェクトやリストに対してのIteratorが提供・出現した場合、毎回のように即時評価でfor-of
したりArray.from()
するなどは現実的ではないでしょう。以下のように書くことも出来ますが、
/** * @template T, U * @param {Iterable<T>} src * @param {function(T):U} mapfn * @return {Iterable<U>} */ function map(src, mapfn) { const iter = src[Symbol.iterator](); const next = function () { const { done, value } = iter.next(); if (done) { return { done, }; } const result = mapfn(value); return { done: false, value: result, }; }; return { [Symbol.iterator]() { return { next, } } }; } const inifinityList = [...]; map(inifinityList);
bind operator proposalがacceptされないことにはa( b ( c() ) )
のように書いていくケースの方が多くてダルい。俺はbind operatorの行く末を気にせずにArray.prototype.map()
のようにメソッドチェーンで書いていきたいんだ、その気持ち、わかります。
というわけで、ものは試しにiterator protocolに則る形で実装してみましょう.
Proof Of Concept
型がややこしいのでTypeScriptで書いてみました。だいたいこんな感じ。どうせES6に型アノテーションつけた程度に書いてるので、適当にアノテーション落とせば動きます。
ちなみに基本設計はRxJS v5を元にしています。IterableとObservableでDualityだ! だからというわけではないですが、評価結果のキャッシュはopt-inになっています(cache()
)。安易にキャッシュしてしまうと、mapやfilterのコールバック関数がDate.now()
などの副作用に依存している場合にバグってしまうので、明示的に使用する前提にしてます。
実用したい場合
大抵の場合は自分で再実装するか、Interactive Extensions (for JavaScript)あたりを待つか、新しい何かが出てくるのを待てば良いのではないでしょうか。
- github:ReactiveX/IxJS: リポジトリ公開されたばかりで今のところどうなるのかよくわからん
他にも、
- github:Reactive-Extensions/IxJS: こっちは古い版でIteration protocols未対応
- linq.js
- lazy.js
などがありますが、2016年3月3日現在の安定版ではiteration protocolには対応してない模様ですが、今のところはES6~のIteratorが必須になるようなケースはそこまで多くはないと思っているので、これらを使うなどで凌ぐというのも現実的だと思います。