Elixirで試しに何か書いてみる(その3) - Elixirのアプリケーション

[Elixirで試しに何か書いてみる(その2)](http://qiita.com/HirofumiTamori/items/7936ed9c6f73f4c9b475)で書いたものが「ちゃんとした書き方ではなかった」ので書き直しました。さらに実行形式ファイルを作れるようにしました。
[GitHubソースも修正済み](https://github.com/HirofumiTamori/vercheckex)です。

**まだまだこのあたりよくわかってないので有識者の方コメントお願いします。**

やり方探してみても本家の[Supervisor and Application](http://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html)も他の知識が十分あることが前提になっていてとっつきにくい。[こちらnikuさんの記事](http://niku.name/articles/2013/10/24/Elixir%20%E3%81%A7%20OTP%20%E3%81%AE%20Application%20%E3%82%92%E4%BD%BF%E3%81%86)が一番役に立ったかもしれません。

続きは
[**Elixirで試しに何か書いてみる(その4) - Taskを使って簡単にする**](http://qiita.com/HirofumiTamori/items/ed131104494f4bd2f695)
[**Elixirで試しに何か書いてみる(その5) - 失敗したらやり直す**](http://qiita.com/HirofumiTamori/items/dde5d95765c68f2fef3e)


-----

## 発端 ##

「そういえば`$ mix new vewcheckex`でなんで`vercheckex/lib/vewcheckex.ex`が作られるのにそこにプログラム書くとなんかエラーになるんだろう」

気にはなっていたのですが今まで`vewcheckex/vewcheckex.ex`を書いて

```bash
$ mix run vewcheckex.ex
```

で動くからそのままにしてました。もしlib/の下にvercheck.exを置くと

```iex
== 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]
[^1]: 実はvercheckex/lib/vercheckex.exではなくvercheckex/vercheckex.exを置いて $ mix run vercheckex.exすると、HTTPotion初め必要なプロセスはコンパイルされて実行されますがvercheckex.exはコンパイルされずにスクリプトのまま後から実行されていたのでした。ですのでこの場合は「必要なあれこれ」が先に起動した後で本体が動くのでエラーにならなかったのです。

では、どうやってそのあたりを設定するのでしょうか。

## プログラムをアプリケーションとして認識させる ##

まず、mixでプロジェクトを生成するときに`--sup`オプションをつけます。supは[Supervisor](http://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html)から来ており自動的にプログラムをApplicationビヘイビアを使うように設定してくれます。ビヘイビアはErlang/OTP用語で、他の言語で言うと「インターフェース」に相当します。

では[前回の投稿](http://qiita.com/HirofumiTamori/items/b72ad232dc3b26e87a03#genserver%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9Ffizzbuzz%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC)の「モジュール名にキャメルケースを使いたい」ところを参考にしてプロジェクトを生成します。

```bash
$ mix new vercheckex --sup --module VercheckEx
```

生成されるvercheckex/lib/vercheckex.exは

```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も

```ex
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`のところに

```ex
mod: {VercheckEx, []}
```
が入っています。これが、アプリケーションとして起動すべきモジュールを指定している箇所です。

## これまでのアプリを書き換える ##

これを踏まえて最低限の書き換えをします。

* lib/vercheckex.exでは今までモジュールの外からモジュール内関数を

```ex
VercheckEx.receiver(result_list, length(urls))
```

のように呼び出していました。これをApplicationビヘイビアのエントリポイントである`main/1'に持ってきます…

```ex
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
```

これで

```bash
$ mix deps.get
...
$ iex -S mix
```

で、`iex`の中からVercheckEx.main/1を呼び出してあげるとアプリが動作するようになりました。

```iex
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に追記します。

```ex
#(前略)#
  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                          # <<<<
#(後略)#
```

これで

```bash
$ mix escript.build
Generated escript vercheckex with MIX_ENV=dev
```

すると./vercheckex(実行可能ファイル)ができます。

## 残る課題 ##

1. `mix`コマンドなどからmainの関数を自動的に呼び出せないか(コマンドラインアプリができたので解決はしているのですが…)
2. 本題のエラー処理、「指定のURLから結果が取れたサイトについては結果を表示し、アクセスに失敗するなどしてエラーになった場合はその旨表示して残りのサイトについては実行を続ける」を実装する。

-----