7つの言語7つの世界 第6章 Erlang セルフスタディやってみた
7つの言語 7つの世界 第6章 Erlang のセルフスタディやってみました。私は Erlang 未経験者だったので最適解ではありませんが、私の解答例を書いておきます。
1日目
探してみよう
Erlang のオフィシャルサイト
Erlang の関数ライブラリの正式ドキュメント
Erlang -- Erlang Reference Manual
Erlang の OTP ライブラリのドキュメント
試してみよう
再帰を用いて文字列を構成する単語の数を返す関数を書け.
単語はスペース区切りとし、スペースの数を数えて最後に + 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 に書き出されます。