ElixirでStreamではないものをStream化するイディオムというものを書いた.
Ruby でも同じことができるだろうか.
単に 5 つ値を取ってみると,全て同じ時刻になってしまう.
Enumerator.new { |y| loop { y << Time.now } }
.lazy
.map { |e| puts e; e }
.first(5)
#> 2015-08-06 12:03:13 +0900
#> 2015-08-06 12:03:13 +0900
#> 2015-08-06 12:03:13 +0900
#> 2015-08-06 12:03:13 +0900
#> 2015-08-06 12:03:13 +0900
# => [2015-08-06 12:03:13 +0900, 2015-08-06 12:03:13 +0900, 2015-08-06 12:03:13 +0900, 2015-08-06 12:03:13 +0900, 2015-08-06 12:03:13 +0900]
そこで wait を入れなければいけない.
処理の流れに直接追加してもいいのだけれど Enumerable::Lazy#zip を使うとやりたいことと,待ち合わせの処理が混ざらないので良いだろう.
Enumerator::Lazy#zip を使うと 2 つの Enumerable の評価が完了していないと次の処理に行けない.
新しく追加する方を 1 秒毎に何かが流れてくるようにする.(内容は何でもよい)
すると必ず 1 秒待ち合わせることになり,結果として 1 秒毎に処理できるようになる.
Enumerator.new { |y| loop { y << Time.now } }
.lazy
.map { |e| puts e; e }
.zip(Enumerator.new { |y| loop { sleep 1; y << nil } }.lazy)
.first(5)
#> 2015-08-06 12:05:52 +0900
#> 2015-08-06 12:05:53 +0900
#> 2015-08-06 12:05:54 +0900
#> 2015-08-06 12:05:55 +0900
#> 2015-08-06 12:05:56 +0900
# => [[2015-08-06 12:05:52 +0900, nil], [2015-08-06 12:05:53 +0900, nil], [2015-08-06 12:05:54 +0900, nil], [2015-08-06 12:05:55 +0900, nil], [2015-08-06 12:05:56 +0900, nil]]
ここで返り値に興味がないのに each
ではなく map
を利用しているのは
以下のメソッドが遅延評価を行う (つまり、配列ではなく Enumerator を返す) ように再定義されています。
map/collect
flat_map/collect_concat
select/find_all
reject
grep
take, take_while
drop, drop_while
zip (※互換性のため、ブロックを渡さないケースのみlazy)
の通り each
では遅延評価が行えないためだ.
zip
で合成するよりは 下のような Enumerator::Lazy#with_interval
を定義して書いた方が Ruby っぽいかもしれない.
class Enumerator::Lazy
def with_interval(second)
interval_stream = Enumerator.new { |y|
times = 0
loop do
sleep second
y << times
times += 1
end
}.lazy
self.zip(interval_stream)
end
end
Enumerator.new { |y| loop { y << Time.now } }
.lazy
.map { |e| puts e; e }
.with_interval(1)
.first(5)
#> 2015-08-06 12:08:33 +0900
#> 2015-08-06 12:08:34 +0900
#> 2015-08-06 12:08:35 +0900
#> 2015-08-06 12:08:36 +0900
#> 2015-08-06 12:08:37 +0900
# => [[2015-08-06 12:08:33 +0900, 0], [2015-08-06 12:08:34 +0900, 1], [2015-08-06 12:08:35 +0900, 2], [2015-08-06 12:08:36 +0900, 3], [2015-08-06 12:08:37 +0900, 4]]
1 行目の Time.now
のところを Net::HTTP.get(URI.parse("http://example.com"))
などにして,3 行目の map
のブロックの処理を変更すれば,定期的な監視を行う簡易的なスクリプトを作ることができるだろう.