こうなったらタスクもやってみましょう。
はじめてなElixir(17) で Agent を使ってみました。プログラミングElixirに出てくる残りの並行プロセス技法は Task です。今日はあまりハマらずで短いです。
なお、プログラミングElixirではAgentより扱いが軽いですが、リファレンス読むとまあまあ他のプロセス系なモジュールと同じぐらいの分量があります。
#前回でいいわすれたこと
備忘録ですが Agent 名は大文字で始めます。これ、裏で勝手に :Elixir が補われるらしく Foo
と書くと :Elixir.Foo
というアトムになるそうな。この中途半端で微妙なおせっかい、Elixir らしいなあと思います。
#勝手に走ってて問い合わせに応答もするプロセスを作る
Agent は自立して走りますが、メッセージを与えるたびに関数が走るので、勝手に何かやってるという感じではありません。(その意味では Agent ってネーミングはあまり適切ではないかもしれません。私が技を知らないだけかもしれませんが)
たとえば前回のエージェントは定期的に(例えば1秒ごとに)関数を起動して状態を変える(リストを伸ばす)ということはしません。
タスクでエージェントを刺激する
これ、毎秒 cast なり update なりを叩けばできます。人間がやらなくても別のプロセスがやってくれれば良いですね。
defmodule Temp12 do
def tick(agent, interval \\ 1000) do
cast(agent)
:timer.sleep(interval)
tick(agent, interval)
end
def start(agent) do
Agent.start(fn -> [0] end, name: agent)
end
def cast(agent) do
Agent.update(agent, __MODULE__.succ())
end
def get(agent) do
Agent.get(agent, &(&1))
end
def succ() do
fn(l) -> [hd(l) +1 | l] end
end
end
tick という関数は指定時間ごとに agent の cast を叩くので、これを非同期に起動してやれば望みのものができそうです。
iex(1)> Temp12.start(Foo) # Agent Foo を動かします
{:ok, #PID<0.106.0>}
iex(2)> Temp12.get(Foo) # Foo の持ってる初期値を表示
[0]
iex(3)> task=Task.async(fn -> Temp12.tick(Foo) end) # 非同期の task を立ち上げます。Foo を刺激します。
%Task{
owner: #PID<0.104.0>,
pid: #PID<0.109.0>,
ref: #Reference<0.906436406.773062658.108199>
}
iex(4)> Temp12.get(Foo) # Foo の状態リストが伸び始めました
[3, 2, 1, 0]
iex(5)> Temp12.get(Foo) # 待ってるとずっと伸びます
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
iex(6)>
とまあ Task と Agent でよろしくやってくれます。こういう、ちょこちょこと書いて組み合わせて合成するのは楽しいです。
Task でもう少し遊ぶ
インターバルタイマでちょっとずつリストを作るというのは、凝ったことをしないで良いなら Stream.interval でできます。
iex(1)> Stream.interval(1000) |> Enum.take(5)
[0, 1, 2, 3, 4]
これ、結果が出てくるのに5秒ほど待たされます。
これに Task の async_stream というのをひっつけます。関数はもらった要素をそのまま返すようにしておきます。
iex(2)> task = Stream.interval(1000) |> Task.async_stream(&(&1))
#Function<1.95549051/2 in Task.build_stream/3>
これでバックグラウンドで task が走ります。
iex(3)> Enum.map(task, &(IO.inspect(&1)))
{:ok, 0}
{:ok, 1}
{:ok, 2}
{:ok, 3}
{:ok, 4}
{:ok, 5}
{:ok, 6}
Enum.map を使って task の結果を見ると毎秒出力がはじまります。全部 :ok をつけてよこしてるのが分かります。だから何だって言われそうですが、わざわざ非同期プロセスを噛ましてストリームしてみたというところです。
Task は途中でなにかやり取りするような関数がなさそうですし、Agent はずっと何か勝手にやり続けるようなことがなさそうですし、やりたいことはどっちかだけじゃ実現できそうにないようです。
あと、Task は Agent と違って async や start のときに name: で名前を付けられませんでした。
#参考文献
漁るといろいろ出てくるので「車輪の再発明」ならぬ「車輪の再発見」を続けてるような気分になります。でもまあ、打って、エラーして、くじけて、調べて、やり直して… しないと身体が覚えないのでね。すべてのエンジニアが車輪の再発見をしてるものと信じてます。