Elixirで試しに何か書いてみる(その3) - Elixirのアプリケーションでアプリの形にしたんですが、肝心のプロセス起動と結果の取得を「手探りの状態で」書いたままにしていました。
naoya@githubさんがElixir のプロセスを使ってフェイルセーフなアプリケーションを作る ─ 失敗は恐れず泥水にダイブのおまけのところでTaskを使ってプロセスの並行処理を簡単に記述する方法を書いてくださったので取り入れてみました。
2015.8.29追記:Timexを最新にするとDate.localの仕様が変わっていて時刻の変換ができないことがわかったので(thanks shibacowさん)、GitHub上のコードを修正しました。
更に続く
Elixirで試しに何か書いてみる(その5) - 失敗したらやり直す
変えたところ
もともと、VercheckEx.main/1の中でプロセスをspawn_link/1で個別に起動しPIDを配列に入れて、それぞれのPIDに対して引数をsendし、VercheckEx.receiverで結果を受信していた…あーややこしい…ところを以下のようにEnum.mapとパイプ演算子でまとめちゃいました。
前のバージョン
def main(args) do
urls = [ #{ URL, type, index}
{"https://github.com/jquery/jquery/releases", :type1},
# (...長いので略)
{"https://github.com/takscape/elixir-array/releases", :type2},
]
# Spawn processes upto the number of URLs
fetchers = for _ <- 0..length(urls), do: spawn_link fn -> VercheckEx.fetch_content() end
Enum.with_index(urls)|>Enum.each( fn(x) ->
{{u,t},i} = x
send Enum.at(fetchers,i), {self, u, t, i}
end)
result_list = []
VercheckEx.receiver(result_list, length(urls))
end
変更後
def main(args) do
urls = [ #{ URL, type, index}
{"https://github.com/jquery/jquery/releases", :type1},
# (...長いので略)
{"https://github.com/takscape/elixir-array/releases", :type2},
]
urls
|> Enum.with_index
|> Enum.map(&(Task.async(fn -> fetch_content(&1) end)))
|> Enum.map(&(Task.await/1))
|> Enum.sort(fn(a,b) ->
{:ok, {_, _, _, i1}} = a
{:ok, {_, _, _, i2}} = b
i1 < i2
end
)
|> Enum.map(&put_a_formatted_line/1)
end
あとはもともと引数を変に組み立てなおしていたところを「ありのままで」使うようにした、とか結果が帰ってきてからソートする部分を追加した、などがありますが、全体として
- プロセス起動のための部分がEnum.map/2とTask.async/1で書けた
- PIDを明示的に管理する必要がなくなった
- 結果を受け取るVercheckEx.receiverがいらなくなった
などかなりすっきりしました。
全体のソースはgithubにあります。
まとめ
やっぱり「用意されているものをちゃんと使う」ことは大事ですね。前回のスクリプトをアプリケーションにする方法もそうでしたがElixirの場合、探してみるとほんとにいろいろうまい仕組みが用意されているので早く「試しに何か書いてみた」の段階から前に進みたいものだなあと思いました。