7つの言語7つの世界 第6章 Erlang セルフスタディやってみた

7つの言語 7つの世界 第6章 Erlang のセルフスタディやってみました。私は Erlang 未経験者だったので最適解ではありませんが、私の解答例を書いておきます。

1日目

探してみよう

Erlang のオフィシャルサイト

Erlang Programming Language

Erlang の関数ライブラリの正式ドキュメント

Erlang -- Erlang Reference Manual

Erlang の OTP ライブラリのドキュメント

Erlang/OTP 22.0

試してみよう

再帰を用いて文字列を構成する単語の数を返す関数を書け.

単語はスペース区切りとし、スペースの数を数えて最後に + 1 したものを出力する関数 word_count/1 を書きます。例によって、コードは gist で貼りつけます。

Emacs から M-x erlang-shell で実行してみました。

Erlang R14B03 (erts-5.8.4) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.4  (abort with ^G)
1> c(word_count).
./word_count.erl:5: Warning: variable 'Anything' is unused
{ok,word_count}
2> word_count:word_count("Erlang").
1
3> word_count:word_count("Hello Erlang.").
2
4> 

再帰を用いて 10 まで数える関数を書け.

実行結果:

Erlang R14B03 (erts-5.8.4) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.4  (abort with ^G)
1> c(count).
{ok,count}
2> count:count(10).
10
3> 

ちゃんと 10 まで数えているはずです。

{error, Message}または success という形式の入力が与えられたとき,マッチングを用いて,“success” または “error: message”のどちらかを出力する関数を 書け.

実行結果:

Erlang R14B03 (erts-5.8.4) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.4  (abort with ^G)
1> c(judge).
{ok,judge}
2> judge:judge(success).
"success"
3> judge:judge({error, "I like Java."}).
"error: I like Java."
4> 

2日目

試してみよう

[{erlang, "a functional language"}, {ruby, "an OO language"}] の ようなキーワード値タプルのリストを考える.リストとキーワードを受け取り,そ のキーワードに対応する値を返す関数を書け.

[{item quantity price}, ...] という形式の買い物リストを考える.このと き,[{item total_price}, ...] という形式の items のリストを構築するリス ト内包表記を書け.ただし,total_price は,quantity×price とする.

Eshell V5.8.4  (abort with ^G)
1> List = [ { sugar, 3, 2.75 }, { solt, 1, 0.75 }, { egg, 24, 0.10} ].
[{sugar,3,2.75},{solt,1,0.75},{egg,24,0.1}]
2> [ { Item, Quantity * Price } || { Item, Quantity, Price } <- List ].
[{sugar,8.25},{solt,0.75},{egg,2.4000000000000004}]
3> 

長さ 9 のリストまたはタプルとして表現された三目並べのボードを読み込むプロ グラムを書け.勝者が確定している場合はその勝者(x または o のどちらか)を, これ以上指し手がない場合は cat を,勝負がついていない場合は no_winner を返 すようにせよ.

ボードを長さ 9 のリストで、空いているマスを u で表現します。x か o が一列に並んでいれば勝者を、そうでなれば空いているマスがあるかどうかを調べて cat か no_winner を返します。

実行例

Eshell V5.8.4  (abort with ^G)
1> c(ticktacktoe).
{ok,ticktacktoe}
2> ticktacktoe:board([x, x, u, u, x, o, u, o, u]).
cat
3> ticktacktoe:board([x, x, o, o, o, x, x, o, o]).
no_winner
4> ticktacktoe:board([x, x, o, u, x, o, u, x, u]).
x
5> 

3日目

3日目のセルフスタディをやる前に プログラミングErlang を読みました。この本を調べれば簡単です。

探してみよう

プロセスが死んだときに,そのプロセスを再起動する OTP サービス

supervisor を使えそうです → http://erlang.shibu.jp/design_principles/supervisor.html

簡単な OTP サーバを構築するためのドキュメント

OTPデザイン原則 — Erlang User's Guide v5.8.1 documentation が参考になります。

試してみよう

translate_service を監視し,死んだら再起動するようにせよ.

OTP の supervisor と gen_server を使います。オリジナルの translate_service.erl は gen_server を使って translator_server.erl として書き直しました。supervisor を使って translate_supervisor.erl を書きました。

実行例

Eshell V5.8.4  (abort with ^G)
1> c(translate_server).
{ok,translate_server}
2> c(translate_supervisor).
{ok,translate_supervisor}
3> translate_server:translate("casa").
** exception exit: {noproc,
                       {gen_server,call,
                           [translate_server,{trans,"casa"},1000]}}
     in function  gen_server:call/3
4> translate_supervisor:start().
translate_server starting
true
5> translate_server:translate("casa").
"white"
6> translate_server:translate("blanca").
"house"
7> translate_server:translate(undefined).
translate_server stopping
translate_server starting

=ERROR REPORT==== 6-Oct-2011::15:00:43 ===
** Generic server translate_server terminating 
** Last message in was {trans,undefined}
** When Server state == 2
** Reason for termination == 
** {{case_clause,undefined},
    [{translate_server,lookup,1},
     {translate_server,handle_call,3},
     {gen_server,handle_msg,5},
     {proc_lib,init_p_do_apply,3}]}
** exception exit: {{{case_clause,undefined},
                     [{translate_server,lookup,1},
                      {translate_server,handle_call,3},
                      {gen_server,handle_msg,5},
                      {proc_lib,init_p_do_apply,3}]},
                    {gen_server,call,
                                [translate_server,{trans,undefined},1000]}}
     in function  gen_server:call/3
8> translate_server:translate("casa").
"white"
9> translate_server:translate("blanca").
"house"
10> 

Doctor プロセスが死んだとき,自分自身で再起動するようにしてみよ.

doctor.erl を immortal_doctor.erl として書き直しました。immortal_doctor は poisoning で死んでも再起動します。

実行例

Eshell V5.8.4  (abort with ^G)
1> c(roulette).
{ok,roulette}
2> c(immortal_doctor).
{ok,immortal_doctor}
3> Doc = spawn(fun immortal_doctor:loop/0).
<0.43.0>
4> Doc ! new.
Creating and monitoring process.
new
5> revolver ! 1.
click
1
6> revolver ! 2.
click
2
7> revolver ! 3.
bang.
3
The shooter <0.45.0> died with reason {roulette,die,at,{15,2,47}}. Restarting. 
Creating and monitoring process.
8> revolver ! 1.
click
1
9> exit(Doc, poisoning).
<0.31.0> poisoned the immortal with reason poisoning.true
 Restarting. 
Creating and monitoring process.
10> revolver ! 1.
click
1
11> 
ボーナス問題

メッセージのログをファイルに記録する基本的な OTP サーバを作成せよ.

OTP に SASL という便利な仕組みがあるので、それを使います。試してみようで作った translate_server.erl と translate_supervisor.erl を SASL でエラーログを吐くように修正します。といっても io:format を error_logger:error_msg に変更しただけです。

さらに、SASL の設定ファイル elog.config を用意します。

あとは、Erlang を起動するときにブート引数で SASL を指定します。

% erl -boot start_sasl -config elog

これで、ログが /var/tmp/elog に書き出されます。