Elixirで試しに何か書いてみる(その2)で書いたものが「ちゃんとした書き方ではなかった」ので書き直しました。さらに実行形式ファイルを作れるようにしました。
GitHubソースも修正済みです。
まだまだこのあたりよくわかってないので有識者の方コメントお願いします。
やり方探してみても本家のSupervisor and Applicationも他の知識が十分あることが前提になっていてとっつきにくい。こちらnikuさんの記事が一番役に立ったかもしれません。
続きは
Elixirで試しに何か書いてみる(その4) - Taskを使って簡単にする
Elixirで試しに何か書いてみる(その5) - 失敗したらやり直す
発端
「そういえば$ mix new vewcheckex
でなんでvercheckex/lib/vewcheckex.ex
が作られるのにそこにプログラム書くとなんかエラーになるんだろう」
気にはなっていたのですが今までvewcheckex/vewcheckex.ex
を書いて
$ mix run vewcheckex.ex
で動くからそのままにしてました。もしlib/の下にvercheck.exを置くと
== Compilation error on file lib/vercheckex.ex ==
** (exit) exited in: :gen_server.call(:hackney_manager, {:new_request, #PID<0.420.0>, #Reference<0.0.1.8689>, {:client, :undefined, :hackney_dummy_metrics, :hackney_ssl_transport, 'github.com', 443, "github.com", [connect_timeout: 5000, recv_timeout: :infinity], nil, nil, nil, true, :hackney_pool, :infinity, false, 5, false, 5, nil, nil, nil, :undefined, :start, nil, :normal, false, false, false, false, nil, :waiting, nil, 4096, "", [], :undefined, nil, nil, nil, nil, :undefined, nil}}, :infinity)
** (EXIT) no process
なんだこれ、さっぱりわからないぞ?gen_serverなんか呼んでないのに?
そもそもElixirのアプリケーションとは
Elixirのプログラムはそれ自体が1個の(Elixir/Erlangの)プロセスで、どこかから起動される必要があります。例えば$iex -S mix
で起動されるとアプリケーションのプロセスも立ち上がってコマンド待ちになります。ただしそこでアプリケーションに必要な他のプロセスを正しく先に起動させていないと上記のようなエラーになります。1
では、どうやってそのあたりを設定するのでしょうか。
プログラムをアプリケーションとして認識させる
まず、mixでプロジェクトを生成するときに--sup
オプションをつけます。supはSupervisorから来ており自動的にプログラムをApplicationビヘイビアを使うように設定してくれます。ビヘイビアはErlang/OTP用語で、他の言語で言うと「インターフェース」に相当します。
では前回の投稿の「モジュール名にキャメルケースを使いたい」ところを参考にしてプロジェクトを生成します。
$ mix new vercheckex --sup --module VercheckEx
生成されるvercheckex/lib/vercheckex.exは
defmodule VercheckEx do
use Application
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
# Define workers and child supervisors to be supervised
# worker(VercheckEx.Worker, [arg1, arg2, arg3])
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: VercheckEx.Supervisor]
Supervisor.start_link(children, opts)
end
end
use Application
でApplication ビヘイビアを使うように指定されdef start(_type, _args) do
でコールバック関数が定義されています。
同じくmix.exsも
defmodule VercheckEx.Mixfile do
use Mix.Project
def project do
[app: :vercheckex,
version: "0.0.1",
elixir: "~> 1.0",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps]
end
# Configuration for the OTP application
#
# Type `mix help compile.app` for more information
def application do
[applications: [:logger],
mod: {VercheckEx, []}]
end
# Dependencies can be Hex packages:
#
# {:mydep, "~> 0.3.0"}
#
# Or git/path repositories:
#
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
#
# Type `mix help deps` for more examples and options
defp deps do
[]
end
end
def application do
のところに
mod: {VercheckEx, []}
が入っています。これが、アプリケーションとして起動すべきモジュールを指定している箇所です。
これまでのアプリを書き換える
これを踏まえて最低限の書き換えをします。
- lib/vercheckex.exでは今までモジュールの外からモジュール内関数を
VercheckEx.receiver(result_list, length(urls))
のように呼び出していました。これをApplicationビヘイビアのエントリポイントである`main/1'に持ってきます…
def main(args) do
urls = [ #{ URL, type, index}
{"https://github.com/jquery/jquery/releases", :type1},
# (長いので略)
{"https://github.com/philss/floki/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
これで
$ mix deps.get
...
$ iex -S mix
で、iex
の中からVercheckEx.main/1を呼び出してあげるとアプリが動作するようになりました。
Compiled lib/vercheckex.ex
Generated vercheckex app
Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> VercheckEx.main []
jquery 3.0.0-alpha1 2015.07.14 <<<<< updated at 7 day(s) ago.
angular 2.0.0-alpha.31 2015.07.15 <<<<< updated at 6 day(s) ago.
...
Nim v0.11.2 2015.05.05
elixir v1.0.5 2015.06.29
floki v0.3.2 2015.06.28
elixir-array 1.0.1 2014.09.23
:ok
iex(2)>
- 次にコマンドラインから起動できる実行可能ファイルを作れるようにmix.exsに追記します。
#(前略)#
def project do
[app: :vercheckex,
version: "0.0.1",
elixir: "~> 1.0",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
escript: escript, # <<<<<<<ここ追加
deps: deps]
end
def escript do # <<<< escript記述追加
[main_module: VercheckEx] # <<<<
end # <<<<
#(後略)#
これで
$ mix escript.build
Generated escript vercheckex with MIX_ENV=dev
すると./vercheckex(実行可能ファイル)ができます。
残る課題
-
mix
コマンドなどからmainの関数を自動的に呼び出せないか(コマンドラインアプリができたので解決はしているのですが…) - 本題のエラー処理、「指定のURLから結果が取れたサイトについては結果を表示し、アクセスに失敗するなどしてエラーになった場合はその旨表示して残りのサイトについては実行を続ける」を実装する。
-
実はvercheckex/lib/vercheckex.exではなくvercheckex/vercheckex.exを置いて $ mix run vercheckex.exすると、HTTPotion初め必要なプロセスはコンパイルされて実行されますがvercheckex.exはコンパイルされずにスクリプトのまま後から実行されていたのでした。ですのでこの場合は「必要なあれこれ」が先に起動した後で本体が動くのでエラーにならなかったのです。 ↩