Hatena::ブログ(Diary)

Twisted Mind

2011-03-19

Erlang に興味を持った人へ

随時加筆してます

追記

2011-06-18
2011-04-02
  • rebar.config の erl_opts から debug_info を消した
  • rebar.config の実際に使っているベースを公開
  • Makefilemake edoc を追加した
  • configure の例を hipe を使わないようにしているので native-lib を外した
  • EUnit について補足を書いた

お前、誰よ

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

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

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

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

学ぶ前に

勘違い

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

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

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

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

ライブラリ

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


スケールとか

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/

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 の設定の紹介

あとは毎回 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 TestErlang 自体をテストするツールとしても使われています。ここでは 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

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

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。