読者です 読者をやめる 読者になる 読者になる

Erlang に興味を持った人へ

erlang

随時加筆してます

追記

2011-06-18
  • rebar.config の erl_opts から fail_on_warning から warnings_as_errors へ変更した
  • rebar.config の xref に fail_on_warning を追加した
  • インストールする Erlang を R14B03 へ変更した
  • ライブラリ紹介に webmachine 、folsom 、ibrowse 、Emysql 、 statebox を追加した
2011-04-02
  • rebar.config の erl_opts から debug_info を消した
  • rebar.config の実際に使っているベースを公開
  • Makefile に make edoc を追加した
  • configure の例を hipe を使わないようにしているので native-lib を外した
  • EUnit について補足を書いた

お前、誰よ

@voluntas といいます。とある零細ベンダーのコンサル/プログラマで、Erlang/OTP を使ってご飯を食べています。一人で書いているわけではなく 4 名のチームで動いています。

Erlang 歴はまだまだ日が浅く 4 年程度で、まだまだ学ぶことばかりですが、Erlang に対して自分が知っていること、感じていることを書き出してみました。

これを読んだ方で是非ともこんなのあるよ、こうしたほうがいいじゃない、これおすすめなどなど教えて頂けると嬉しいです。

ちょっとやってみたい方へ

学ぶ前に

  • まずは飛行機本を読んでください、これはチュートリアルではありません
    • 飛行機本を読んで「アプリケーション」を作りたいなと思った人向けの話です
  • 初めて学ぶプログラミング言語Erlang を選択するのは間違いです
  • ネットワーク関連のシステムを作る必要があるのであれば Erlang はお勧めです
  • 基本的に英語の資料やソースを読む事が多いです
  • ライブラリが整備されておりませんので github 等から自分で探し出すことになります
  • Makefile の知識はありますか? Erlang はコンパイル言語ですので是非覚えておくと良いです

勘違い

よくスケールしないのであれば Erlang を使えばいいよと言っている人がいますが、その勧めている人は Erlang を使ったことが無いうえに、スケールという概念をかなり勘違いしていると思います。なのでスケーラビリティが欲しくて Erlang を始めるのは間違いです。スケールについては下の方で追記しておきました。

まずは飛行機本を買いましょう、飛行機本を買えないのであれば Erlang を学ぶのはあきらめた方が良いくらい良著です

洋書でも良ければ以下の 2 冊をお勧めします

ネットにも文章は沢山ありますがここでは絞って紹介させて貰います

ライブラリ

お勧めのライブラリです。Rebar 化されていないものは統一性が無くなるのでどんなに良いツールでも使わない方がいいと思います。

  • rebar
    • A sophisticated build-tool for Erlang projects that follows OTP principles
    • Erlang の統合ビルドツールです
  • basho_bench
    • A load-generation and testing tool for key-value stores
    • KVS 用のベンチマークですがいろいろな用途に使えます
  • proper
    • PropEr: a QuickCheck-inspired property-based testing tool for Erlang
    • QuickCheck というプロパティベース(型からランダムにテストを生成する)のテストツールです
  • eper
    • Erlang performance and debugging tools
  • meck
    • A mocking library for Erlang
    • モックライブラリです EUnit と組み合わせると強力です
  • getopt
  • mochiweb
  • webmachine
    • A REST-based system for building web applications.
    • ウェブ API サーバを作るためのフレームワークErlangAPI サーバ作るならこれが一番オススメです
  • folsom
    • Expose Events and Metrics via HTTP and JSON
    • 統計情報を簡単に扱えるようにするフレームワーク、JSON で簡単に統計情報が取得できるようになります
  • ibrowse
    • Erlang HTTP client
    • HTTP クライアントはデフォルトでも入っていますが、自分の知る限りこれが一番使いやすいです
  • Emysql
  • statebox
    • Erlang state "monad" with merge/conflict-resolution capabilities. Useful for Riak.
    • マルチノード下でデータがコンフリクトした際に、それぞれのノード下でそのデータに対する今までのそれぞれのオペレーションを記録しておき、オペレーションを時間軸で並び直してデータに対して処理をしてコンフリクトを解決する素敵なアプリケーション。Riak 向けと書かれていますが汎用的に出来ています

スケールとか

Erlang は確かにスケールします。これは間違いありません。CPU 800 % とか使います。しかし CPU を使う事が大事なのではなく CPU をうまく使う事が大事です。さらに Erlang の世界はかなり広いのでちょっと学んだだけでスケールするシステムは書けません。

もちろんこの部分だけ Erlang で書く ... というのはありです。ただし連携するのが面倒かも知れませんのでそのあたりも考えてからの導入をお勧めします。

ただ勘違いして欲しくないのはスケールが必要だから Erlang を選ぶというのは間違いです。スケールが必要なのであれば C/C++/Java 等でマルチスレッドプログラミングを学んだ方が良いです。またボトルネックになるのが CPU である場合はそんなに無いはずです。ほとんどは I/O 周りではないでしょうか。それがわかって Erlang をやるのであれば良いですが、何もわからずただ「 Erlang スケールする」でやるのは間違いです。

なぜ Erlang なのか

とにかく特定要素に特化していることもあり、コードが短くて済みます。また文法が単純な事もあり可読性が高いです。さらに軽量プロセスを使って気軽に並列処理を書くことが出来ます。

また、軽量プロセスの監視機能が優秀です。この監視機能だけでも使うメリットがあります。

とにかくネットワークサーバを書くことに特化していてかゆいところに手が届く言語です。またエリクソン主導で完全なオープンソースとして提供され、安定したリリースをされていることも魅力の一つです。

結局の所、自分が必要としている道具としてピッタリあった、ただそれだけなのかも知れません。

OTP の壁

Erlang を学ぶ際に問題となってくるのは OTP だと思います。OTP を用いたシステム設計が出来るかどうかが一番の難関です。これは「ただ Erlang」が書けるだけでなく、システムを1から設計したことが無いと難しいです。

基本的には L4 ~ L7 まで面倒を見る必要が出てきますので、知識を得てから再挑戦するか知識を得ながら挑戦するのどちらか、自分は後者です。

OTP を学ぶにはアクターモデルをしっかり理解する必要もあります。一つ一つの軽量プロセスに役割を割り当てそれらとメッセージパッシングで通信しながら処理を行います。

それらは基本的に学べる物ではなく色々作ってみるしかありません。一番いいのは製品として使われているソースコードを読むことです。

また洋書と @shibukawa と @ymotongpoo が翻訳してくれている文章を読むのもいいかもしれません。

ですが、何より「作ってみる」事でしか学べませんので本や人から学ぶのを早めにあきらめるのがお勧めです。

ソースコードリーディング

もしソースから勉強したいのであれば mochiweb が今のところ一番お勧めです。HTTP の知識以外は求められませんし、JSON ライブラリや色々便利なライブラリが入っていますので Erlang の知識があれば読み進めることが出来るでしょう。

Erlang のインストール

Erlang のインストールは R14B03 が今の最新版です。基本的には最新版を使うようにするのが良いです。またソースから入れる習慣も付けると良いと思います。

ソースは公式は遅いので手前味噌ですが Dropbox にミラーしてありますのでどうぞ。

Mac での configure の例

  ./configure --prefix=/opt/erlang/R14B03 --disable-dynamic-ssl-lib --enable-threads --enable-smp-support --enable-kernel-poll --disable-sctp --disable-hipe --enable-darwin-64bit --without-javac --with-termcap

追記(2011-03-30): @akitada からアドバイスをいただきました。 --disable-hipe してるのに --enable-native-libs しても意味ないとのこと。言われてみればその通りなので、--enable-native-libs は外しておきます。

何か無い限りは 64bit でインストールするのをお勧めします。また Jinterface という Java との連携を使わなければ --without-javac するのが良いでしょう。また hipe はあまり効果が無いこともあって有効にするメリットはありません。

Erlang のフォルダ構造

まずトップレベルのフォルダ名も重要です application 名としてなります。ここではサンプルとして snowflake を使いたいと思います。

最低限のフォルダ構成

snowflake/
  README
  Makefile
  rebar.config
  rebar

まずは何はともあれ README ですね、どんなアプリケーションなのかどうかを明記しましょう。rebar については rebar の導入を参考にしてください

よくあるフォルダ構成

snowflake/
  README
  Makefile
  rebar.config
  rebar
  test/
  ebin/
  include/
  src/
  • test にはモジュールのテストファイルが入ります、テストについては eunit の導入を参考にしてください
  • ebin にはコンパイル済みモジュールが入ります
  • src にはソースコードが入ります
  • include には .hrl という拡張子のヘッダファイルが入ります

rebar の導入

rebar とは riak という商用分散 KVS を開発している Basho が開発した Erlang ビルドツールです。これが出てきたおかげで Makefile を苦労して書いたりする必要が無くなりました。

導入方法は二つあります。 github から rebar のソースコードを持ってくるか、または github から公開されている rebar ファイルをダウンロードしてくるかのどちらかです。

ここではソースコードをダウンロードしてビルドする方法を紹介しておきます。

$ git clone git://github.com/basho/rebar.git
$ cd rebar
$ make

これでフォルダの中に rebar というファイルが出来たと思います。こちらを先ほど作った snowflak フォルダに追加してください。

また、rebar.config は色々あると思いますが、お勧めの rebar.config を公開しますのでこちらを使ってみてください。かなり最低限ですが十分だと思います。色々使ってみて覚えてみてください。

追記(2011-04-02): erl_opts にdebug_info はデフォルトになり、明示的に書く必要が無くなりました

rebar.config

{erl_opts, [fail_on_warning,                                                    
            warn_export_all]}.
{xref_checks, [undefined_function_calls]}.
{cover_enabled, true}.
{clean_files, ["ebin/*", ".eunit/*"]}.

追記(2011-04-02): 自分が実際に使っている rebar.config のベース、そのうち github に公開します

{require_otp_vsn, "R14"}.
{erl_opts, [warnings_as_errors, warn_export_all, warn_untyped_record]}.     
{xref_checks, [fail_on_warning, undefined_function_calls]}.
{clean_files, [".qc/*", ".eunit/*", "ebin/*.beam"]}.
%% Jenkins 向け
%% {eunit_opts, [{report,{eunit_surefire,[{dir,"."}]}}]}.
{cover_enabled, true}.
{edoc_opts, [{dialyzer_specs, all}, {report_missing_type, true},                  
             {report_type_mismatch, true}, {pretty_print, erl_pp},
             {preprocess, true}]}.
{validate_app_modules, true}.
{deps,
  [{meck,
    ".*", {git, "git://github.com/eproxus/meck.git", {branch, "master"}}},
   {proper,
    ".*", {git, "git://github.com/manopapad/proper.git", {branch, "master"}}}
  ]}.

rebar.config の設定の紹介

  • erl_opts
    • warnings_as_errors
      • Warning をコンパイルエラーとして扱います
    • warn_export_all
      • export_all を設定しているも十がある場合はコンパイルエラーとして扱います
  • xref_chekcs
    • fail_on_warning
      • Warning を Error 扱いにします
    • undefined_function_calls
      • 他のモジュールも含め存在しない関数呼び出しがあった場合コンパイルエラーとして扱います
  • cover_enabled
  • clean_files
    • ./rebar clean (つまり make clean) した際に指定したファイルを削除してくれます

あとは毎回 rebar ファイルを実行するのが面倒なので Makefile を作成します

追記(2011-04-02): edoc が日本語使えるようになっていたので、記載しておきます

Makefile

all: clean compile xref eunit                                                   

compile:
        @./rebar compile

xref:
        @./rebar xref

clean:
        @./rebar clean

eunit:
        @./rebar eunit

edoc:
        @./rebar doc

rebar のテンプレート機能を使う

フォルダ構成は決まりましたがこのままではソースコードがありませんね。色々書いていくのも面倒なのでここでは rebar の入っているテンプレート機能を使いましょう。snowflake フォルダ直下で以下の二つのコマンドを実行してください

$ ./rebar create-app appid=snowflake
==> snowflake (create-app)
Writing src/snowflake.app.src
Writing src/snowflake_app.erl
Writing src/snowflake_sup.erl
$ ./rebar create template=simplemod modid=snowflake
==> snowflake (create)
Writing src/snowflake.erl
Writing test/snowflake_tests.erl

そして make と打ってください。

$ make
==> snowflake (clean)
==> snowflake (compile)
Compiled src/snowflake_sup.erl
Compiled src/snowflake.erl
Compiled src/snowflake_app.erl
==> snowflake (xref)
==> snowflake (eunit)
Compiled src/snowflake_app.erl
Compiled test/snowflake_tests.erl
Compiled src/snowflake_sup.erl
src/snowflake.erl:6: Warning: export_all flag enabled - all functions will be exported
make: *** [eunit] Error 1

上記のようなエラーが出るはずですこのエラーは export_all が自動生成されたモジュールに書かれてしまっているからです。消すと正常にコンパイル出来ると思います。

src/snowflake.erl から -compile(export_all). を削除してください。

そして再度 make と打ってください

$ make
==> snowflake (clean)
==> snowflake (compile)
Compiled src/snowflake_sup.erl
Compiled src/snowflake.erl
Compiled src/snowflake_app.erl
==> snowflake (xref)
==> snowflake (eunit)
Compiled test/snowflake_tests.erl
Compiled src/snowflake_app.erl
Compiled src/snowflake_sup.erl
Compiled src/snowflake.erl
  There were no tests to run.
Cover analysis: /private/tmp/snowflake/.eunit/index.html

これで正常にコンパイルが出来ました。あとは作りたいシステムを作っていくだけです。

EUnit の導入

Erlang に標準で付いてくるテストは Common Test と EUnit があります。Common Test は Erlang 自体をテストするツールとしても使われています。ここでは EUnit を使います。理由としてはシンプルでわかりやすい。さらに日本語のドキュメントが存在するという理由です。

また EUnit は他の言語に存在する xUnit とほとんど変わりませんので勉強コストが低いというのもあります。

rebar は EUnit のテストも簡単にしてくれます。さらに Cover モジュールを使いカヴァレッジも自動で生成してくれます。

EUnit のドキュメントは @shibukawa が翻訳してくれています。この二つを読めばある程度理解できると思います。

EUnit には実はテスト以外に便利なツールがあります。まずはその紹介をしてから EUnit の紹介に写りたいと思います。

デバッグマクロというものが EUnit には入っています。これを使うには -include_lib("eunit/include/eunit.hrl"). をソースコードに書く必要があります。

-module(snowflake).

-author('@voluntas').

-export([main/0]).

-include_lib("eunit/include/eunit.hrl").

main() ->
  A = 10,
  ?debugVal(A),
  ok.

?debugVal というマクロは A = 10 というのを画面に表示してくれます。つまりプリントデバッグを簡単にしてくれるのです。またこのマクロはコンパイルオプションで NODEBUG を渡すことで無効になります。

rebar.config では erl_opts に {d, 'NODEBUG'} のように書けば NODEBUG を付けてコンパイル出来ます。

{erl_opts, [{d, 'NODEBUG'},
            fail_on_warning,                                                    
            warn_export_all,
            debug_info]}.

Erlang ではよっぽど複雑に書かない限りはプリントでバッグで十分です。それを簡単にしてくれるツールを紹介させて頂きました。

さて、ユニットテストの書き方に戻ります。実は rebar の導入のところで snowflake モジュールを作ったときに test/snowflake_tests.erl というモジュールが同時に生成されます。

EUnit は test/ にある *_tests.erl というモジュールを自動的にテストモジュールとして判断してくれます。またそのモジュールの中の関数名も *_test() または *_test_() で終わる関数を自動でテスト関数と認識してくれます。

実際に EUnit の機能をもう少し見ていきましょう。

EUnit は主に二種類のテストにおける状態を管理する方法があります。

一つは setup です。初期化関数が呼ばれてから全てのテストが実行され最後にクリーンアップが呼ばれます。
もう一つは foreach です。テスト一つ一つに対して繰り返してセットアップとクリーンアップが呼ばれます。

基本的にはこちらの二つを使う事になります。

それ以外にテストの制御をする機能がいくつかあります

順番にテストするのが inorder でそれぞれのテストを並列に実行するのが inparallel です。
また、たまに使うのが timeout です。
Erlang にはメッセージパッシングの際に after を定義するとタイムアウトが指定できます。
これは指定した時間内に何かしらのメッセージを受け取らなかった場合はなんかしらの処理をするといったものです。

この場合のテストに timeout を使います。10秒後に受け取らなかったらこの値を返す場合は timeout を 20 くらいに定義しておいて、値をチェックして下さい。

EUnit のタイムアウト時間はとても短いのである程度時間がかかりそうな処理は全て timeout を使うと良いでしょう。

(書きかけです)

パッケージ管理について

私はパッケージ管理は使ったことがありません。なぜならちょちょいとつくるツールとかを Erlang で作る事がないため基本的には rebar の deps 機能を使ってライブラリを引っ張ってきているからです。ツールはもっぱら Python で書いています。

ただ無いわけではありません。Agner というのが最近の流行でそちらを日本語で紹介されている記事があります。(thx @snakeman)

Erlang パッケージ管理ツール Agner を試す
http://snakemanshow.blogspot.com/2011/03/erlang-agner.html

プロトタイプを気軽に作る、またはライブラリを簡単に使う等のお手軽さを考えるとパッケージ管理も便利です。