LoginSignup
9
3

More than 5 years have passed since last update.

Elixirの無名関数の中でreceiveをし続ける

Posted at

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 を宣言する程のものではない

とは書きましたが,無名関数で再帰するためのコードは知ってないと読みにくい面があるので普通に modulefunction でやった方がわかりやすいことが多いと思いました.

無名関数で再帰を書いてしまったら,名前のある関数へと変えた方が読みやすいのではないかと立ち止まって考えるとよいかもしれません :thinking:

9
3
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
9
3