スポンサーリンク

2015年9月16日

[Elixir]Streamの使い方を習得する

とある錬金術師の万能薬(Elixir)

Goal

Streamの使い方を習得する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

Wait a minute

今回はStreamの回。
遅延評価について学ぶとまではいかないが、基本的な使い方を習得する。
その他、Enumableについて(申し訳程度に)少しだけやる。

Index

Stream
|> What is Stream?
|> Let’s use the Stream!

What is Stream?

怠惰(lazy)?遅延評価?Streamって何よ?
別にパイプ演算子をつなげたり、eachな処理をしたいならEnumなんかを使えばよくね?
しかし、Enumにも不都合(?)な点がある。
  • Enumで処理すると、全ての操作で中間リストが作られる点
例えば、以下のような実行をしたとする。
  • 1: 1~100までの範囲(Range)のリストが作られる
  • 2: 偶数のみのリストが作られる
  • 3: 全ての要素を足し合わせた結果が作られる

Example:

iex> 1..100 |> Enum.filter(fn(x) -> rem(x, 2) == 0 end) |> Enum.sum
2550
上記の実行では、途中途中の中間リスト(結果)が作成され次の処理に渡されている。
大概の場合、問題を感じることもないと思う。
私も感じたことはない。大したことをやってないとも言えるが(笑)
だが、非常に量が大きかったり、無限なものを扱うとなると適切ではないようです。
そこで、Streamと言うものが出てくる。
Streamは、一連の処理として作成できる。
そして、Enumオブジェクトへ渡した時にだけ実行される。
つまり、結果を作っているのではなく、処理を作っていると言える。
1~4の足し算を例に取って、それぞれのイメージをしてみる。
  • Enumのイメージ
    逐一、結果に対して処理をするイメージ。
1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
  • Streamのイメージ
    一括で結果を出すイメージ。
(((1 + 2) + 3) + 4) = 10

Example:

EnumとStreamを実行した時の出力を見てみよう。
iex> range = 1..100
1..100
iex> Stream.map(range, &(&1 * 2))
#Stream<[enum: 1..100, funs: [#Function<45.113986093/1 in Stream.map/2>]]>
iex> Enum.map(range, &(&1 * 2))
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42,
 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82,
 84, 86, 88, 90, 92, 94, 96, 98, 100, ...]
どっちが良いではなく、上手く使い分けましょうってだけだと思います。
知識として取り入れるのは難しくないと思いますが、使い方や使い道を考えるのは結構大変そうですね(汗)

Note:

ついでに、少しEnumableについてまとめておく。
  • プロトコルである
  • ポリモーフィックである
  • Enumerableプロトコルを実装していれば、Enumerableできるデータ型で動作する

Let’s use the Stream!

Streamを使ってみましょう。
実行結果が同じで、つまらないと思いますが、
まずEnumの中間リストでやった内容をStreamに書き直してみましょう。

Example:

iex> 1..100 |> Stream.filter(fn(x) -> rem(x, 2) == 0 end) |> Enum.sum
2550
Stream.filter/2の段階では実行されていない。
だから、遅延処理と言われるわけですね。(納得)
ちょっとした注意点があります。
無限に繰り返すStreamの関数には、一部のEnumの関数へ渡してはいけないものがあります。
例えば、Stream.cycle/1と言った関数があるのですが、
名前の通り、無現に循環するStreamを作成します。

Example:

iex> Stream.cycle([1,2,3]) |> Enum.take(10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
iex> Stream.cycle([1,2,3]) |> Enum.take(100)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2,
 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, ...]
1~3を繰り返すStreamをEnum.sum/1関数に渡したらどうなるでしょうか?
試しに実行しましたが、処理が終了しませんでした(笑)
無限ループに入ってしまうことがあるので、
Enumへ渡すStreamが有限なのか無限なのか考えるべきでしょう。
Streamに関してはここまでとします。
詳しい使い方が知りたければ、ドキュメントを見ることを推奨します。

Speaking to oneself

やる前は無限を扱うだの遅延評価だのと、
身構えてしまったけど、使うだけ、覚えるだけならそんなに難しくないですね。
しかし、使いこなすのはかなり難しそうですが(汗)

Bibliography

人気の投稿