33
11

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 1 year has passed since last update.

fukuoka.ex Elixir/PhoenixAdvent Calendar 2020

Day 18

Elixirのプログラムの実行方法

Last updated at Posted at 2020-12-18

この記事は、fukuoka.ex Elixir/Phoenix Advent Calendar 2020 の 第18日目の記事です。

昨日は @dimanche さんの 「Elixirでナンプレを解くコードを書いてみた」 でした。

明日は @akkiii さんの 「RubyプロダクトをElixirプロダクトへリプレイスする作業を自動化しよう①」です。


2020年も終わりが近づいてきました。
去年(2019年)の10月くらいから Elixir、12月くらいから Nerves を触り始め、「あっ」という間に一年がたってしまいました。

↓何度も読み返しているプログラミングElixirは一年でこんな感じになりました。
(付箋って貼る時が見返したい気持ちのピークで後で見返すことってあまりないですね(笑
image.png

さて、どんなことを書こうかと頭をひねったのですが、Elixirのプログラムの実行方法で迷ったことがあるのでそれについて書くことにしました。

プログラムの実行方法

例えば、プログラムをC/C++で書こうとするとそれがライブラリでなければ、必ずmain関数を持ちます。
このmain関数がプログラムのエントリポイント、起点になります。
hello worldも複雑なプログラムもmain関数をもち、それをコンパイルしたバイナリをキックすることで実行できます。

一方、Elixirでhello worldを実行しようとすると、どういった方法があるでしょうか?

hello worldを出力する関数を持つモジュールを書いて、

  • IExで実行する
    • これはモジュールをインタプリタ−で呼び出すだけです。
  • mix taskにする
    • mix helloで呼び出せるように作ることができます。
    • ただ、作るだけだとそのプロジェクトのルートディレクトリでのみしか使えません。プロジェクト外でも使いたい場合は mix archive.build/installが必要になります1。(mix phx.new や mix nerves.newがどこにいても使える理由ですね。
  • escriptにする
    • 実行バイナリを作り、実行する方法です。2(このバイナリを実行するためにはerlangのインストールが必要ですが、elixirのインストールは必要ありません。
  • applicationにする
    • hello world でapplicationを使うのは不向きですが、ここでは方法のひとつとして挙げています。

ぱっと、思いつくだけで4通りあります。(書き進めるなかで、mix runでexsを実行する方法を知りました
一つのやりたいことに対してその手段が複数あるというのは、始めたてのころは悩みやすいのではないかと思います。

私はそもそもどのような手段があるのか知らなかったので悩みました。

それでも、一年もすると使いどころが分かってきます。
それぞれの使いどころを紹介します。こういう使い方もできるよというのがあればぜひ教えてください。

それぞれの使いどころ

IExの使いどころ

関数のマニュアル読みとその試行に使います。
プログラムを書いている最中に、「あー、文字列分割ってどうやったっけ?」となったら、別terminalでiexを開き、

iex) h String. # タブ連打、それっぽい関数がないか調べます。「お、splitっぽい」と見つけることができます。
iex) h String.split # 関数の使い方を調べて
iex) String.split("Elixir", "i") # 試します。
["El", "x", "r"] # およ、"i"が抜けてしまいました。
iex) h String.split # もっかい調べて
iex) String.split("Elixir", ~r/i/, include_captures: true) # 再度、試します。
["El", "i", "x", "i", "r"] # yey

自身の作ったモジュールのお試しもできます。

# プロジェクトルートで
$ iex -S mix # を実行するとプロジェクト内のモジュールをコンパイルして読み込んだ状態でiexが立ち上がります。
# Phoenixなら
$ iex -S mix phx.server # とするとPhoenixを立ち上げつつ、iexを触ることができます。

mix taskの使いどころ

単発実行なスクリプトを書く時に使っています。
ファイルのコンバートとかに便利です。

アイデアとして持っていて作っていないのは、オレオレプロジェクトのスケルトンをつくるmix taskです。
いつも使うライブラリ(git_hooksとかmix_test_watch)などを予めmix.exsに書きだしてプロジェクトの雛形を作る

mix oreore.new

があってもよいなと思っています。

追記 2022/11/05

escriptの使いどころ

見いだせていません。。
開発者間で機能を共有するなら(したことないですが)mix taskでよいと思いますし、ユーザーにバイナリを配布するケースがでたとしてもErlangのインストールを前提とするとちょっと躊躇します。

追記 2022/11/05

  • Erlang がインストールされているのであれば、ちょっとしたコマンドラインツールを作るのに便利に思えてきました。

applicationの使いどころ

mix taskは単発実行するスクリプトを作るために使いますが、applicationは機能を提供し続けるサーバーを作る場合に使います。

Elixirでプログラムを組む場合、作りたいものは概ねapplicationになるのではないかと思います。
Phoenixもapplicationです。


ここまでが使いどころです。

iexは想像がつくと思いますし、mix taskの作り方はドキュメントに従えば簡単です。escriptは、、略

applicationの作り方は(私には)とっつきにくいところがあったので、ちょっと紹介します。

applicationの作り方と実行

作り方

applicationとなるプロジェクトは以下の手順で作ることができます。

  1. mix new でプロジェクトを作る
  2. use Applicationを使ったapplication.exを作る
  3. エントリポイントとして2で作ったモジュールを指定する

順に見ていきましょう。

mix new

コマンドを実行するだけで変わったことはしません。

$ mix new my_app
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/my_app.ex
* creating test
* creating test/test_helper.exs
* creating test/my_app_test.exs

application.exを作る

ファイルを作成し、エディタで開き

$ cd my_app
$ mkdir lib/my_app
$ vim lib/my_app/application.ex

以下のように書きます。

defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    children = []
    Supervisor.start_link(children, opts)
  end
end

use ApplicationでApplicationを使うことを宣言し、最低限必要な関数であるstartを書きます。
start関数はApplicationを使うときに実装しなければならないコールバックです。

この「実装しなければならないコールバック」は何が起きているのかが暗黙的で抵抗感を持つと思いますがOTPを利用するために一旦飲み込みましょう。※start関数が呼ばれるタイミングは次の節で説明します。

このstart関数で行っていることは、

  • "MyApp.Supervisor"を名前をつけたSupervisorを起動する

だけです。

現時点でのコードではSupervisorが監視するchildrenは空のリストのため監視対象は存在しません。

エントリポイントとなるモジュールを指定する

mix.exsをエディタで開き、作成したMyApp.Applicationをエントリポイントするよう追記します。

#中略
  def application do
    [
      mod: {MyApp.Application, []}, # 追記、このように書くとエントリポイントになります。
      extra_applications: [:logger]
    ]
  end
#中略

このプログラムを実行するとMyApp.Applicationモジュールのstart関数がエントリポイントとなり実行されます。

実行

iex -S mix

まず、iex -S mixで実行してみます。

iex -S mix
Erlang/OTP 23 [erts-11.1.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> # 起動しました。

どのように起動しているかみるために、observerで見てみます。

iex()>:observer.start()

image.png

名前をつけたMyApp.Supervisorが起動していることが確認できます。

寄り道:childrenの追加

ちゅっと寄り道をしてchildrenの追加をしてみます。
一度、iexを止め、lib/my_app/gen_server.exを作り、以下のように書きます。
(記述したのは、5秒おきに標準出力に"hello"を出力するGenServerです。

defmodule MyApp.GenServer do
  use GenServer

  def start_link(state) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def init(state) do
    send(self(), :hello)
    {:ok, state}
  end

  def handle_info(:hello, state) do
    IO.puts("hello")
    Process.send_after(self(), :hello, 5000)
    {:noreply, state}
  end
end

次にapplication.exのchildrenに追加して、

defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    children = [{MyApp.GenServer, []}]
    Supervisor.start_link(children, opts)
  end
end

再度、iex -S mixから:observer.start()をすると

image.png

作ったGenServerがMyApp.Supervisorに監視されていることが確認できます。
このように、childrenに追加することでにさまざまな機能を持ったGenServerやSupervisor等をぶら下げていくことができます。

寄り道をやめて、実行のしかたに戻ります。

mix run --no-halt

mix runは--no-haltオプションを使うことで持続するプログラムを起動できます。

$ mix run --no-halt
hello
hello
hello
...

mix app.start(追記:持続するアプリには使えないことが分かりました

mix app.startは--permanentオプションを使うことで持続するプログラムを起動できそうですが、うまくできていません。

調査中

質問して教えてもらいました。

--permanentオプションは持続させるためのオプションではなく、mix app.startのふるまいはmix runのようなものでした。

よって、持続するapplicationを実行するには mix run --no-halt を使う必要があります。

mix release

mix run --no-haltを使う場合は、プロジェクトごとデプロイしなければいけませんし、デプロイ先にErlang, Elixirがインストールされていなければなりません。

これを解決するのが、mix releaseです。

$ mix release
Compiling 3 files (.ex)
Generated my_app app
* assembling my_app-0.1.0 on MIX_ENV=dev
* skipping runtime configuration (config/runtime.exs not found)

Release created at _build/dev/rel/my_app!

    # To start your system
    _build/dev/rel/my_app/bin/my_app start

Once the release is running:

    # To connect to it remotely
    _build/dev/rel/my_app/bin/my_app remote

    # To stop it gracefully (you may also send SIGINT/SIGTERM)
    _build/dev/rel/my_app/bin/my_app stop

To list all commands:

    _build/dev/rel/my_app/bin/my_app

mix releaseはErlang VMとその上で動作するプログラム一式をまとめて出力してくれます。
このため、手元とデプロイ先のCPUアーキテクチャが同じであれば、デプロイするだけで動作させることができます。
動作コマンドは上記にあるとおりです。

寄り道:applicationという考え方について

Elixirというか元はErlangだと思うのですが、プログラムを構成する考え方が他の言語と異なっていて面白いなと最近気づいたことがあります。

C,C++を考えると必ずmain関数からプログラムは開始し、そのプロセスを起点にスレッドやフォークをすることで並行プログラミングをすると思います。

↓イメージ図
image.png

一方でErlang VM上で動作するApplicationはメインとなるApplicationのエントリポイントこそありますが、プログラムの開始後、明示的に呼び出すことなく各Applicationが独自に並行に走り出します。

↓イメージ図
image.png

例えば、ログを吐くためにLoggerを使うと思いますが、これもApplicationとして作られており、mix.exsでは以下のように使用が宣言されています(プロジェクトのテンプレートとしてmix newした時から存在します)。

#中略
  def application do
    [
      mod: {MyApp.Application, []},
      extra_applications: [:logger] # ここで使用を宣言、お手製のApplicationも追加できます。
    ]
  end
#中略

Elixirでない言語でライブラリというと、それは呼び出す関数をまとめたものだと思いますが、Elixirにおいて(Erlangもそうなのかな?)は並行して走るApplicationもライブラリになります。

なので、メインとなるプロジェクトに、自分で作った別のApplicationをライブラリとして使うことができます。
私はこの違いに気づいた時に「面白いな」と思いました。そう思いませんか?

まとめ

本記事ではElixirのプログラムの実行方法を眺め、Applicationの作り方、使い方をちょっと掘ってみました。

Elixirは知れば知るほどパワフルで楽しい言語だなと思います。

これからもElixirでプログラミングしていきましょう♪
Happy Hacking!!

  1. 詳細は mix help archive.build/installを見てください。

  2. 詳細は mix help script.build や https://elixirschool.com/ja/lessons/advanced/escripts/

33
11
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
33
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?