はじめに
私はPythonのプログラムを使う時、breakを使う事がよくあります。
繰り返し処理の中で、繰り返しを中断したい状況になったら、直ぐに中断できて便利ですよね
Elixirには、繰り返し文(Pythonのfor, while等)がありません。なので繰り返しを中断するためのbreak文がありません。
Elixirでは、どうしているか、例題を設定して、Elixirの場合の記述を書いてみます。
課題1
heavy_process()があります。
"heavy!"と表示します。
trueかfalseを返す関数です。
これを10回実行して10回ともtrueが返ってきたら、true, 一度でもfalseが返ったらfalseを返す関数を作成する。
シンプルな実装
def get_result_enum() do
1..10
|> Enum.map(fn _i -> heavy_process()end)
|> Enum.all?()
end
def heavy_process() do
IO.puts("heavy!")
hd Enum.take_random([true, false], 1)
end
end
実行結果
iex> Tutorial012.get_result_enum
heavy!
heavy!
heavy!
heavy!
heavy!
heavy!
heavy!
heavy!
heavy!
heavy!
false
「一度でもfalseが返ったらfalseとする。」を自分では作成せず、Enum.all?に任せています。
プログラムを見ただけで、処理の内容が理解できるとおもいます。
Enumって楽しいですね。
課題2
課題1に、
heavyと表示するのは、非常に大変な処理で、できるだけ実行回数を少なくしたい。
という条件が追加されたらどうでしょうか?
reduce_whileを使う
Enumには、途中でやめれれるreduce_whileがあります。
:haltを返せば中断できます。
def get_result_reduce_while do
1..10
|> Enum.reduce_while(false, fn _i, _acc ->
ret = heavy_process()
if ret, do: {:cont, true}, else: {:halt, false}
end)
end
実行結果
iex> Tutorial012.get_result_reduce_while()
heavy!
false
heavyの呼び出しが、10回ではなくなってます。
目的は達成できてます。しかし、なにかわざわざ複雑なものを使ってる感じです。
reduce_whileを無理使ってるような。
accとか使ってないのでreduceの必要性もありません。
これだったら、Pythonのbreakの方がよっぽどわかりやすい。
例外を使った脱出
Elixirでは、try-throwを使えば、処理を中断する事ができます。
heavy_process()を実行して、falseが返ったら、throwして中断するようにしてみます。
Enum.map()やEnum.all?()を使う必要はないので、forで書いてみます。
※Elixirのforは、Pythonの内包のforのようなものです。
def get_result_for_with_break() do
try do
for _i <- 1..10 do
ret = heavy_process()
if ret == false do
throw :break
end
end
true
catch
:throw, :break -> false
end
end
実行結果
iex> Tutorial012.get_result_for_with_break()
heavy!
false
heavyの呼び出しが、10回ではなくなってます。
中断できています。
Streamを使う方法
try-catchを使った処理でやりたいことはできています。
悪くはないと思います。
Enum.all?()はリストの要素をひとつづつ確認していきますが、一つでもfalseがあると処理を中断してfalseを返します。
遅延評価をするStreamを使う事で、課題2を実現できます。
def get_result_stream() do
1..10
|> Stream.map(fn _i -> heavy_process()end)
|> Enum.all?()
end
実行結果
iex(22)> Tutorial012.get_result_stream()
heavy!
false
heavyの呼び出しが、10回ではなくなってます。
Enum.all?()の記述のわかりやすさを生かしつつ、中断もできています。
Elixirって楽しいですね。
遅延評価
課題1のプログラムが10回実行しているのは、Enum.all?()の前に、10回実行した結果を作っているためです。
all?()には、課題2で解決したい、余計な処理をしない仕組みが備わっています。
Enum.all?()がリストの内容を実際に調べる時に都度、heavy_process()を呼び出すようにできれば、無駄な実行をなくせます。
これを実現するのが、遅延評価です。
Pythonにも遅延評価(yield)があるので、同じことはできると思います。
しかし、不幸か幸いか、Pythonには、break文があるので、for文でbreakする実装をいつもしていたので、やったことはありません。
やりたい事 | Elixir | Python |
---|---|---|
繰り返しの中断 | try-catch | break |
遅延評価 | Stream | yield |
まとめ
- 遅延評価を使う事で、そもそも中断処理が不要にできる事もある。
- Elixirで、break的な事がしたくなったら、try-throwを使えばできる
- 繰り返しを中断できるreduce_whileを使うのも一つの方法
- Elixirは楽しい!