Elixirの無名関数で再帰するを再構成
新しいプロセスを作り,そのプロセスがメッセージを待ち続ける(reveive
し続ける)ようにしたい.
わざわざ module と function を宣言する程のものではない.
といった時に便利な,無名関数で receive
し続けるためのイディオムです.
[できない] x という無名関数宣言の中で,宣言中の x という関数を指定する
ぱっと思いつくのはこんな書き方なのですけど,これはできないようです.
iex(1)> x = fn
...(1)> n when n <= 0 -> exit(:normal)
...(1)> n ->
...(1)> IO.puts n
...(1)> x.(n - 1) # <- これができない
...(1)> end
** (CompileError) iex:5: undefined function x/0
(elixir) src/elixir_fn.erl:33: anonymous fn/3 in :elixir_fn.expand/3
(stdlib) lists.erl:1239: :lists.map/2
(stdlib) lists.erl:1239: :lists.map/2
iex(1)>
[できるが使いにくい] 関数を引数に取る無名関数 x を宣言しておき,x を呼び出すときに x を引数に渡す
読みにくさを競うようなタイトルになってしまいました.
x.(5, x)
といったようなことができるような無名関数 x
を作るということです.
これはできます.
でも第二引数に x
を渡すことや,その理由の説明が大変で,ちょっと使いにくいですね.
iex(1)> x = fn
...(1)> n, _ when n <= 0 -> exit(:normal)
...(1)> n, f ->
...(1)> IO.puts n
...(1)> f.(n - 1, f)
...(1)> end
#Function<12.52032458/2 in :erl_eval.expr/5>
iex(2)> x.(5, x)
5
4
3
2
1
** (exit) normal
iex(2)>
[使いやすい] 無名関数 y を宣言し,その中で x を宣言.利用するときは x のことを意識せずに y のみを使う
使う人が中でどんなことが行われているかを気にせずに使えるようにラップします.
y.(5)
のように使えるようになります.
iex(2)> y = fn m ->
...(2)> x = fn
...(2)> n, _ when n <= 0 -> exit(:normal)
...(2)> n, f ->
...(2)> IO.puts n
...(2)> f.(n - 1, f)
...(2)> end
...(2)> x.(m, x)
...(2)> end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(3)> y.(5)
5
4
3
2
1
** (exit) normal
iex(3)>
無名関数の中で receive する
再帰する無名関数が書けたので receive し続ける無名関数も書けるはずです.
iex(3)> receive_loop = fn to_pid ->
...(3)> x = fn f ->
...(3)> receive do
...(3)> "bye" -> exit(:normal)
...(3)> x -> send(to_pid, x)
...(3)> after 10000 ->
...(3)> send(to_pid, "any commands?")
...(3)> end
...(3)> f.(f)
...(3)> end
...(3)> x.(x)
...(3)> end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(4)>
nil
iex(5)> me = self()
#PID<0.81.0>
iex(6)> {:ok, pid} = Task.start_link(fn -> receive_loop.(me) end)
iex(7)> send(pid, "hello~")
"hello~"
iex(8)> flush()
"hello~"
:ok
iex(9)> ## 10秒待つ
nil
iex(10)> flush()
"any commands?"
iex(11)> send(pid, "bye")
"bye"
iex(12)> ## 10秒待つ
nil
iex(13)> flush()
:ok
iex(14)>
- 複数回送っても反応してくれる
- タイムアウトしてくれる
ので完成としまーす.
まとめ
最初に
わざわざ module と function を宣言する程のものではない
とは書きましたが,無名関数で再帰するためのコードは知ってないと読みにくい面があるので普通に module
と function
でやった方がわかりやすいことが多いと思いました.
無名関数で再帰を書いてしまったら,名前のある関数へと変えた方が読みやすいのではないかと立ち止まって考えるとよいかもしれません