18
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-07-22

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(実行可能ファイル)ができます。

残る課題

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

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

18
15
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
18
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?