gen_tcp:controlling_process/2
Erlangを触る時間がなかなかとれずに心身に深刻なダメージが蓄積してきたので、久しぶりにいじることにしました。今まで何度書いてきたか分かりませんがポートフォワーディングを書いています。
双方向からのデータを相手に転送することを考えると、{active, true}にしておいて受信データはメッセージで貰ったほうが都合がよいため、別プロセスを立ち上げて gen_tcp:controlling_process/2 してみたのですがどうも期待通りに動きません。こんな感じのコードでした:
accept_loop(LSock) -> {ok, Sock} = gen_tcp:accept(LSock), spawn(fun() -> gen_tcp:controlling_process(Sock, self()), accepted(Sock) end), accepted_loop(LSock). accepted(Sock) -> receive {tcp, Sock, Bin} -> 以下略
いろいろ試してみたところ、accept_loopの方のプロセスにメッセージが飛んできているようで、 controlling_process が効いていないようでした。戻り値チェックしていないことにようやく気付いて調べてみたところ、 {error, not_owner} なる結果が。そうだったのか… と今更知りました。現在制御プロセスであるプロセスから他のプロセスに制御を渡すことしかできないのですね…
初めて使ったわけでもないのになんで今更そんなことに気付くのだろうと不思議に思ってソースを追ってみたところ、どうやら R13B03 でチェックが追加されたようです。しかしそれも4年半前のことですので、今までどうやって気付かずに生きてきたんだろうと思いました…。
Erlangとジャンケン
SequenceableCollection >> #after: を使ってジャンケンの勝敗判定経由でPrologの話をしよう - CROSS2012・言語CROSSにあるジャンケン判定をErlangで真似てみました。
-module(janken). -compile(export_all). win(goo, choki) -> true; win(choki, par) -> true; win(par, goo) -> true; win(_, _) -> false. judge(P, P) -> even; judge(P1, P2) -> J1 = win(P1, P2), J2 = win(P2, P1), if J1 -> winner_is_p1; J2 -> winner_is_p2; end.
実際に動かしてみます…
3> janken:judge(goo, choki). winner_is_p1 4> janken:judge(goo, goo). even 5> janken:judge(goo, par). winner_is_p2
よいようです。しかし単にパターンマッチングをしているだけなので面白くないですね…。
オリジナルのPrologソースを少しいじってみました。
win(goo, choki). win(choki, par). win(par, goo). even(goo, goo). even(choki, choki). even(par, par). judge(P1, P2, winner_is_p1) :- win(P1, P2). judge(P1, P2, winner_is_p2) :- win(P2, P1). judge(P1, P2, even) :- even(P1, P2).
zinnia@tulip:/tmp[5]% gprolog GNU Prolog 1.3.0 By Daniel Diaz Copyright (C) 1999-2007 Daniel Diaz | ?- ['janken.pl']. compiling /tmp/janken.pl for byte code... /tmp/janken.pl compiled, 11 lines read - 1144 bytes written, 18 ms (1 ms) yes | ?- judge(goo, par, Result). Result = winner_is_p2 ? a % グー対パーではパーが勝つ。 no | ?- judge(par, par, Result). Result = even % パー対パーは引き分け。 yes | ?- judge(P1, par, winner_is_p1). % 相手(P2)がパーを出したときに勝てる手は? P1 = choki ? a % チョキ no | ?- judge(P1, P2, winner_is_p2). % 相手(P2)が勝つ手をすべて挙げる。 P1 = choki P2 = goo ? a P1 = par P2 = choki P1 = goo P2 = par no | ?- judge(P1, P2, Result). % すべての手と勝敗を挙げる。 P1 = goo P2 = choki Result = winner_is_p1 ? a P1 = choki P2 = par Result = winner_is_p1 P1 = par P2 = goo Result = winner_is_p1 P1 = choki P2 = goo Result = winner_is_p2 P1 = par P2 = choki Result = winner_is_p2 P1 = goo P2 = par Result = winner_is_p2 P1 = goo P2 = goo Result = even P1 = choki P2 = choki Result = even P1 = par P2 = par Result = even yes
こういう柔軟な問い合わせができるのはやっぱりPrologならではですね。
Erlangと状態
Erlangにおけるアンチパターン(なのかどうか悩んでいるもの)についていくつかねたがあるので、今回はその1つ「状態を持つ機能の実現方法」について紹介します。
Erlangでは変数を書換えることはできないため、ある変数を加工する場合には新しい変数を用意することになります。
1> D = dict:new(). {dict,0,16,16,8,80,48, {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}, {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}} 2> D = dict:store("Yama", "Kawa", D). ← Dと新しいDはマッチしない ** exception error: no match of right hand side value {dict,1,16,16,8,80,48, {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}, {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[], [["Yama",75|...]]}}} 3> D1 = dict:store("Yama", "Kawa", D). ← 新しい変数を用意すればOK {dict,1,16,16,8,80,48, {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}, {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[], [["Yama",75|...]]}}} 4>
dictのように内部状態がどんどん変わるようなものは、内部状態を呼出し側に戻してやって、都度それを渡してもらうようにする必要がありますが、以下のような使い勝手上の問題があります。
- 新しい変数を都度用意するのは煩雑。うっかり古い変数を使ってしまったりするとトラブルのもと。自然とfold系の関数を多用することになり、あまり見通しがよくない。
- 利用者側には見せる必要がない内部状態を利用者側に管理させることになる。勝手にいじられる危険性など。
- (dictには存在しないが)内部状態を変えつつ、本来の戻り値を返したい場合にはタプルでまとめる等の工夫が必要。内部状態が変わるかどうかは利用者には関係のない話なのに、戻りの形式が変わってしまうので意識せざるを得ない。
これらの問題は、gen_serverを使えばある程度回避することができます。
続きを読むErlangとInverse Fizzbuzz
Inverse FizzbuzzをErlangでやってみることにします。Inverse Fizzbuzzとは、fizz/buzz/fizzbuzz の組み合わせを与えたとき、それを満たすような最短の数列を得るというものです(途中のfizz/buzz/fizzbuzzのいずれでもない項は省く)。たとえば、[fizz] であれば [3] ですし、[fizz,buzz]であれば、[9,10]となります([3,4,5]も[fizz,buzz]ですが、最短ではないため解にはなりません)。
問題だけを見て考えたのは下記のような方針に基づいた解法でした。
- とりあえず指定した数値以降で条件を満たす最小の組み合わせを得る関数を作る(inversefizzbuzz_core)
- それを1〜100までひととおり評価させて、考えられる回答(ただし最短とは限らない)の組み合わせを得る(inversefizzbuzz_all)
- その組み合わせの中で、最短かつ最小のものを求める。(inversefizzbuzz0)
以下、できあがったものを順番に展開してゆきます。まず、補助関数として、以下2つを準備しました。
- 指定した数値以降で、条件(fizz/buzz/fizzbuzz/any)を満たす最小の値を得る(next)。anyは、fizz/buzz/fizzbuzzいずれでもOKとする。
- 指定した数値がfizz/buzz/fizzbuzzのいずれの条件を満たすかを調べる(what)
それぞれ下記のようになりました。
続きを読むErlang/OTP R15Bで例外発生時の行番号を知る
R15B が昨年12月に出ました。NewsのHighlightsを見ると、「Line number and filename information are now included in exception backtraces.」という一文が。
どれどれ...
-module(test). -compile(export_all). test_fail() -> A = 1, B = 2, A = B. test() -> try test_fail() catch Class:Reason -> io:format("~p:~p ~p~n", [Class, Reason, erlang:get_stacktrace()]) end.
34> c(test). {ok,test} 35> test:test_fail(). ** exception error: no match of right hand side value 2 in function test:test_fail/0 (test.erl, line 7) 36> test:test(). error:{badmatch,2} [{test,test_fail,0,[{file,"test.erl"},{line,7}]}, {test,test,0,[{file,"test.erl"},{line,11}]}, {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,576}]}, {shell,exprs,7,[{file,"shell.erl"},{line,668}]}, {shell,eval_exprs,7,[{file,"shell.erl"},{line,623}]}, {shell,eval_loop,3,[{file,"shell.erl"},{line,608}]}] ok
すごい..!!! これはうれしいですね。今までは、例外発生時に行情報が欲しければsmart_exceptionsを使うという選択肢があったものの、常に使い続けるのはなかなか難しいものがあったと個人的には思っています(以前ちょっと使ってみて、それっきりになっておりました)。標準できちんと情報をくっつけてくれる恩恵は非常にでかいですね。
しかしR15Bが出ていることに気付くのに3ヶ月もかかっているのでひどい話です。8コアマシンにErlang入れようとしてやっと気付いた有様です。
Erlangとハフマン符号 (3)
今回は前回の続きと、encodeを並列化する話です。
(前回のあらすじ)decodeを早くしようと、もともと1ビットずつのパターンマッチをしていた部分を、複数ビットを一度にマッチして1バイト分復号できるような無名関数を作って試してみたらオリジナルよりずっと遅かったという結果になりました。(あらすじ終わり)
遅くなった原因は、無名関数を呼んでいるからか、パターンマッチの個数が増えた+複雑になったからか、いずれかだと考えました。無名関数でなくするために、いったん出力させた関数をソースに埋め込んで実行させるようにしてみました。ファイルが変わるたびに毎回手で入れ替えなければいけないので実用上の意味はありませんが、fun使うものと直接呼ぶものの違いは分かると思います。
続きを読むErlangとハフマン符号 (2)
前回の続きです。
decodeを改良しようとして失敗した記録です。特に使うあてがあるわけではありませんが、もうちょっと高速化するあてはないもんかと思いました。
なんとなく1ビットずつマッチさせてるのが非効率な感じがしました。ビット列→1バイトの変換表はハフマン木が決まれば作れるわけで、それを元に、たとえば下記のような関数を作れば一度に複数ビットをマッチングできるのでいいのではないかと思いました。
fun (<<0:1, Rem/bitstring>>) -> {<<65:8>>, Rem}; (<<2:2, Rem/bitstring>>) -> {<<67:8>>, Rem}; (<<3:2, Rem/bitstring>>) -> {<<66:8>>, Rem} end.
まず、文字列で上記のような関数を組み立てて、それをevalして無名関数にしたてあげてみることにします。
続きを読む