Elixir 1.12で追加されたMix.install/2の使い道
Elixir 1.12で追加されたMix.install/2
という関数が、どういう時に便利なのかについて、確認してみたいと思います。
Mix.install/2
とは
Elixir 1.12からMix.install/2
という関数が追加されました(ドキュメント: Mix — Mix v1.12.2)。
それまでは、外部のモジュールを使いたい時はmix.exs
に追加した上で、mixプロジェクトとして起動する必要がありました。
Mix.install/2
を用いると、mix.exs
に依存を追加せずとも、Elixirスクリプト内で以下のように書いておくと、実行時にモジュールのインストールが行われ、スクリプト内で利用できるようになりました。
Mix.install([
:decimal,
{:jason, "~> 1.0"}
])
初回実行時には依存モジュールのダウンロードが実行されるため、時間がかかります。次回以降はキャッシュから読み込むので、速いです。
Elixirスクリプトで依存モジュールを読み込む
スクリプト内でのMix.install/2
のユースケースと実行例については、以下の記事をご参照ください。
- Mix.install/2 で お手軽に依存ライブラリ(Hex) をインストールしてElixirプログラムをイゴかす - Qiita
- Elixir1.12.0-otp-24のMix.install/2でNxライブラリを試してみる - Qiita
Erlangノードに依存モジュールを注入する
Erlangノードとは、他のErlangノードと通信可能なErlangのランタイムのことをいいます(Elixirでいうと、iex --sname foo
とかして起動すると立ち上がるものです)。
分散されたErlangシステムは互いに通信するいくつものErlangランタイムシステムを含んでいます。 これらのそれぞれのランタイムはノードと呼ばれます。
このノードに対して、依存モジュールを注入してやるということをしてみましょう(「注入」といっても、対象となるノード上でMix.install/2
を実行するということなので、コード自体を「注ぎ入れる」というのとはイメージが違いますが)。
実際にやってみる
Erlangノードをふたつ立ち上げて、実験してみましょう。
一方のターミナルで、以下のようにしてノードを起動します。
$ iex --sname node1
iex(node1@keyaki)1>
もう一方でも、以下のようにしてノードを起動します。
$ iex --sname node2
iex(node2@keyaki)1>
node1
からnode2
に接続します。ちゃんと繋がっているのも確認しましょう。
iex(node1@keyaki)1> Node.connect(:node2@keyaki)
true
iex(node1@keyaki)2> Node.list
[:node2@keyaki]
ここで、node2
でJSONを扱うライブラリであるjasonを用いて、JSONデータをデコードしてみることを試みます。
iex(node2@keyaki)1> Jason.decode("{\"foo\": \"bar\"}")
** (UndefinedFunctionError) function Jason.decode/1 is undefined (module Jason is not available)
Jason.decode("{\"foo\": \"bar\"}")
このように、エラーになります。jasonは、標準では読み込まれない依存モジュールなので、当然です。
ここで、node1
からnode2
に対して、この依存モジュールを注入してみましょう。
iex(node1@keyaki)3> Node.spawn(:node2@keyaki, fn -> Mix.install([:jason]) end)
#PID<11553.121.0>
Resolving Hex dependencies...
Dependency resolution completed:
New:
jason 1.2.2
* Getting jason (Hex package)
==> jason
Compiling 8 files (.ex)
Generated jason app
何やら依存モジュールがHexリポジトリからダウンロードされてインストールされたようです。node1
でそれが行われているように見えますが、実際にはnode2
で実行された内容がnode1
側に印字されているだけです。
node2
でもう一度JSONをデコードしてみましょう。
iex(node2@keyaki)1> Jason.decode("{\"foo\": \"bar\"}")
{:ok, %{"foo" => "bar"}}
今度はエラーなくJSONをデコードできました。
Mix.install/2
の制約
Mix.install/2
のドキュメントには、以下の通りの記載があります。
This function can only be called outside of a Mix project and only with the same dependencies in the given VM.
つまり、mixプロジェクトとして起動されたノードで実行することはできません。すでに読み込まれた依存関係にあとから追加してしまうと、整合性が取れなくなってしまい得るからなのでしょう。
同様に、一度Mix.install/2
を実行した後に、別の依存モジュール(や、同じものでもバージョン違い)をインストールすることもできません。
iex(node2@keyaki)2> Mix.install([:decimal])
** (Mix.Error) Mix.install/2 can only be called with the same dependencies in the given VM
(mix 1.12.2) lib/mix.ex:454: Mix.raise/2
(mix 1.12.2) lib/mix.ex:544: Mix.install/2
このように、エラーになります。
これは何のためにあるのか
端的には、まずはLivebookのユースケースを満たすために開発されたものなのだろうと思います。Livebookは、Elixirで実装された、JupyterLabのようにドキュメントツールと統合されたElixirのコード実行環境です。
Livebookでは、デフォルトのElixir Standaloneモードでは、ノートブックごとに異なるノードを起動して、そのノード上でコードを実行します。なぜそんなことをしているかというと、Livebookが動いているVM上でコードを実行すると、異なるノートブック間で書いたコードが影響しあってしまい、不都合だからです(Nervesのような環境で動かすために、あえてそれを可能にするEmbeddedモードというのもあります)。
そのように、コードの実行を別途起動したノード上で行うことで、ノートブックのコードの実行環境がそれぞれ分離された状態を担保できるわけです。また、そのことにより、Elixir StandaloneモードではMix.install/2
によってノートブック内で動的に依存モジュールを読み込むことができます。まっさらなノードを起動した上で、ノートブックのコードに必要な環境をいちから作れるようにしているわけですね。
このように、コードをメインのノードとは分離された環境で実行しつつ、実行する側のノードにおいても必要なモジュールを簡単に追加できるようにするのに、Mix.install/2
は便利です。Livebookを用いない場合でも、上記の「Erlangノードに依存モジュールを注入する」で示したように、コードをノード間で分散実行したい場合に、コードの実行に必要なモジュールをセットアップするのに使えます。Elixirによる分処理散基盤を、より便利に使うのに役立つ機能であると思います。
おわりに
本記事では、Mix.install/2
とは何かについて説明しつつ、Elixirの分散処理基盤における使い道について検討しました。Elixirって、とっても便利ですね。
Discussion