この記事は、fukuoka.ex Elixir/Phoenix Advent Calendar 2020 の 第18日目の記事です。
昨日は @dimanche さんの 「Elixirでナンプレを解くコードを書いてみた」 でした。
明日は @akkiii さんの 「RubyプロダクトをElixirプロダクトへリプレイスする作業を自動化しよう①」です。
2020年も終わりが近づいてきました。
去年(2019年)の10月くらいから Elixir、12月くらいから Nerves を触り始め、「あっ」という間に一年がたってしまいました。
↓何度も読み返しているプログラミングElixirは一年でこんな感じになりました。
(付箋って貼る時が見返したい気持ちのピークで後で見返すことってあまりないですね(笑
さて、どんなことを書こうかと頭をひねったのですが、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
- mix task の制限は、
mix archive
を使いグローバルなタスクを作る場合には、deps を利用できないことです。
escriptの使いどころ
見いだせていません。。
開発者間で機能を共有するなら(したことないですが)mix taskでよいと思いますし、ユーザーにバイナリを配布するケースがでたとしてもErlangのインストールを前提とするとちょっと躊躇します。
追記 2022/11/05
- Erlang がインストールされているのであれば、ちょっとしたコマンドラインツールを作るのに便利に思えてきました。
- priv ディレクトリのリソースはescriptに持ち込めない点に注意が必要です。
- 作った escript: https://github.com/pojiro/file_watch
applicationの使いどころ
mix taskは単発実行するスクリプトを作るために使いますが、applicationは機能を提供し続けるサーバーを作る場合に使います。
Elixirでプログラムを組む場合、作りたいものは概ねapplicationになるのではないかと思います。
Phoenixもapplicationです。
ここまでが使いどころです。
iexは想像がつくと思いますし、mix taskの作り方はドキュメントに従えば簡単です。escriptは、、略
applicationの作り方は(私には)とっつきにくいところがあったので、ちょっと紹介します。
applicationの作り方と実行
作り方
applicationとなるプロジェクトは以下の手順で作ることができます。
- mix new でプロジェクトを作る
- use Applicationを使ったapplication.exを作る
- エントリポイントとして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()
名前をつけた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()をすると
作った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関数からプログラムは開始し、そのプロセスを起点にスレッドやフォークをすることで並行プログラミングをすると思います。
一方でErlang VM上で動作するApplicationはメインとなるApplicationのエントリポイントこそありますが、プログラムの開始後、明示的に呼び出すことなく各Applicationが独自に並行に走り出します。
例えば、ログを吐くために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!!
-
詳細は mix help archive.build/installを見てください。 ↩
-
詳細は mix help script.build や https://elixirschool.com/ja/lessons/advanced/escripts/ ↩