はじめに
本記事は、Elixir Advent Calendar 2015 の記事です。Elixir のバージョンは v1.1.1 を前提としています。
オープンソースのここが面白い
改めて語るまでもないのですが、個人的に、オープンソース・ソフトウェアの面白みというのは、以下が挙げられるかと思います。
- 自分のコードを世界に向けて公開できる
- 他人のコードを改変できる
- 世界のエンジニアとの間でコミュニケーションが生まれる
Elixirの面白さ
Elixirはたくさんの面白さが詰まった言語ですが、とりわけオープンソースのプログラミング言語としてのElixir、そしてそのエコシステムの面白みというのは、個人的には以下が挙げられます。
- Homoiconicである (ElixirはElixirで書かれている)
- 仕様が一通り落ち着いたのが最近である
Homoiconicであるがゆえ、言語処理系のソースコードリーディングを、Elixirだけで実施することが可能です。言い換えれば、Elixirの処理系のソースを見るだけで、Elixirの書き方の勉強にもなります。
また、仕様が落ち着いたのが昨年あたりで、それまでは(特に0.1x系では)激動の時代でした。
今回ここに焦点を当ててみようと思います。
Elixirエコシステム 過去の遺産
GitHubで language:Elixir
と検索すると、Elixirで書かれたオープンソース・プロジェクトは、現時点で 6,839 あります。例えば伝統ある Erlang が 15,003 であることを鑑みると、非常に勢いがある、と言えるのではないでしょうか。
ただ、この勢いに、取り残されてしまったプロジェクトも存在します。Elixir v0.x の頃に書かれたまま、メンテナンスされていないプロジェクトです。こういったプロジェクトは、特にv1.0以降のElixirで使用したくても、言語仕様が変わってしまっているために、動作しません。(コンパイルできません。)
ステレオタイプな日本人的な物言いをすれば、まさに「勿体ない」「直せば使える」ということで、「オープンソースとしてのElixirエコシステムの歩き方」というテーマのもと、このようなプロジェクトをメンテナンスしてみようと思います。
OOP
今回の題材は、OOP こと elixir-oop とします。マクロとGenServerを使うことで、Rubyのようなクラス構文と、単純ではありますがクラスのインスタンス化を実現することができます。
こんなに面白い試みがあるのに、最後のコミットは2年以上前。Elixir v0.10.2 が想定されており、当然、最新のElixirでは動作しません。
まさに、Elixirの勢いに取り残されてしまったプロジェクトであると言えるでしょう。
進め方
以下の流れで進めます。
- GitHubで elixir-oop をforkする
- 依存関係のあるプロジェクトを fork する
- 依存関係あるプロジェクトを修正する
- elixir-oop の mix.exs における deps を、fork して修正を施した自分のプロジェクトに向ける。
- elixir-oop を修正する
- fork元に対して、それぞれ Pull Request を出す。
デバッグ, 修正, デバッグ, 修正 ...
OOPと依存関係のあるプロジェクト
-
meh/elixir-managed_process
- Process.Managed - garbage collected processes for Elixir
-
meh/elixir-finalizer
- Finalizers for Elixir
-
tonyrog/resource
- Resource tracking
tonyrog/resource
リソースのトラッキングを行い、リソースが destroy されたら、デストラクタがコールバックを送るNIF (Native Implemented Function) です。
NIFということで、こちらは C と Erlang で書かれており、修正の必要はありませんでした。
meh/elixir-finalizer
tonyrog/resource の、Elixir向けのラッパーです。Elixir v0.12.5 を前提としており、v1.1.1 ではコンパイルできません。
結果として、以下の修正を行いました。
meh/elixir-managed_process
meh/elixir-finalizer からコールバックを受けた場合に、プロセスを終了できるようにします。プロセス指向におけるGCを実現します。
こちらは、以下の修正を行いました。
shurizzle/elixir-oop
そして本丸の OOP です。修正内容は以下の通り。
- Fix codes and successfully compile with Elixir-v1.1.1, and avoid fixing the version of dependancies
- Fix an error when one of the macros is called from other applications
- Fix message passing operator
- Replace a deprecated function
動作確認
OOP のREADMEにあるものと同じコードで、動作確認を行います。
$ mix new oop_demo
......
defmodule OopDemo do
use OOP
class LULZ do
attr lulz
attr_writer lol1
attr_reader [lol2, :lol3]
def initialize do
@lulz = "STUFF"
@lol1 = 1
@lol2 = 2
@lol3 = 3
end
end
end
$ mix deps.get
$ mix compile
==> resource (compile)
==> finalizer
Compiled lib/finalizer/supervisor.ex
Compiled lib/finalizer/manager.ex
Compiled lib/finalizer.ex
Generated finalizer app
==> managed_process
Compiled lib/managed_process.ex
Generated managed_process app
==> oop
Compiled lib/oop.ex
Generated oop app
== Compilation error on file lib/oop_demo.ex ==
** (CompileError) lib/oop_demo.ex:14: invalid expression in match
expanding macro: OOP.@/1
lib/oop_demo.ex:14: OopDemo.LULZ.new/0
(elixir) lib/kernel/parallel_compiler.ex:100: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/8
initialize
関数内のインスタンス変数定義が0ないし1の場合はコンパイルできるのですが、これを複数定義している場合に、このようなコンパイルエラーになります。
精査した結果、 defmacro @
か、 Kernel.defp transform
あたりに問題があると見ていますが、ちょっと根が深そうなので、今回はここまで。
今後の計画
今直面している問題を含め、問題を全て解消できたら、forkした全てのプロジェクトに対して、それぞれ修正版を Pull Request します。
まとめ
オープンソースならではの、Elixirエコシステムの楽しみ方を紹介しました。
初学者が一からアプリケーションを書くのは大変、ということも少なくないと思います。
Elixir自体のバグフィックスも、それはそれで非常に勉強になりますが、Pull Requestを出すにしても緊張します。(mergeされた時の達成感も非常に大きいです!)
こういったElixirで書かれたソフトウェアの修正の方が、よりイージーでカジュアルに取り組むことができ、Elixirへの理解も十分深まると思っています。実際、今回の取り組みにより、自分もマクロとGenServerに対する理解が深まったと感じています。
よかったら、ぜひトライしてみて下さい。
The world is full of fascinating problems waiting to be solved.1 (この世界は解決を待っている魅力的な問題でいっぱいだ2)