Elixir初心者です。「Programming Elixir ≥ 1.6」のβ版を読んでいて、Enum.reduceについての説明が多少理解できない点があったので自分なりに解釈しました。そのメモです。まあ、英語力の問題も大きいのですけど。
以下のcreate_processes関数ですが、引数で渡ったnの個数だけプロセスを作ります。そのとき、1番目のプロセスには自分自身(親)のpidを渡します。2番目のプロセスには1番目のプロセスのpidを渡します。3番目のプロセスには2番目のプロセスのpidを渡します。これを繰り返して最後にn番目のプロセスをつくり、そのpidをlastに入れます(厳密にはマッチか)。
そうしてn個のプロセスを作成し終えたら、親プロセスからlastにメッセージを送ると、どんどん前のプロセスにメッセージを送っていき、最後には自分自身(親)にメッセージが送り返されるという処理になっています。メッセージにはカウンターがついているので、何個のプロセスがチェインで結ばれているかがわかるというものです。まあ、n個なんですけど。
defmodule Chain do
def counter(next_pid) do
receive do
n ->
send next_pid, n + 1
end
end
def create_processes(n) do
last = Enum.reduce 1..n, self(),
fn (_,send_to) ->
spawn(Chain, :counter, [send_to])
end
send last, 0 # start the count by sending a zero to the last process
#
end
まあ、一応公式サイトの説明を載せておきます。これも英語で、しかも翻訳してもわかりにくいような気がします。
公式サイト Enum
reduce(enumerable, acc, fun)
Invokes fun for each element in the enumerable with the accumulator.
The initial value of the accumulator is acc.
The function is invoked for each element in the enumerable with the accumulator.
The result returned by the function is used as the accumulator for the next iteration.
The function returns the last accumulator.
つまり言葉での説明はわかり難いので、reduceの定義をを少し抽象化して以下のような式で表現すれば一発で理解できると思います。
reduce [1,2,3,4..n] init fun = (fun n ... (fun 4 (fun 3 (fun 2 (fun 1 init)))) ...)
funはspawnでプロセスをつくりpidを返しますので、例えば(fun 3 (fun 2 (fun 1 init)))は3番目のプロセスのpidを表し、accumulatorとして(fun 4)の引数になります。initはaccumulatorの初期値で、この場合はself()で自分自身(親)のpidになります。ちなみにenum要素の1,2,3..nはダミーの引数として扱われfunのなかでは使われていませんね。
ちなみにこれを実行すると、以下のように100万個のプロセスを生成することができます。デフォルトでは20万個ぐらいですけど、コマンドラインで簡単に拡張できるのですね。さくらのVMの安いサーバなのですが、Elixirのプロセスは凄過ぎるね。45921695マイクロ秒だからメッセージの伝達も速いですね。
elixir --erl "+P 1000000" -r chain.exs -e "Chain.run(1_000_000)"
{45921695, "Result is 1000000"}
昔、「プログラミングHaskell」(だったと思う)を読んだとき、foldr(foldl)をこのような式で説明してあって、大変直感的でわかりやすいと思いました。(不思議と他の本ではこのような説明がなかったのですが。)誤りがあったらご指摘いただければ助かります。