LoginSignup
15
15

More than 5 years have passed since last update.

1秒毎に処理をするEnumerator

Last updated at Posted at 2015-08-07

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 を利用しているのは

class Enumerator::Lazy

以下のメソッドが遅延評価を行う (つまり、配列ではなく 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 のブロックの処理を変更すれば,定期的な監視を行う簡易的なスクリプトを作ることができるだろう.

15
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
15