Hatena::ブログ(Diary)

no_orz_no_life

2014-05-05

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年半前のことですので、今までどうやって気付かずに生きてきたんだろうと思いました…。

2014-01-31

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).

GNU Prologで実行してみます。

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ならではですね。

2012-06-28

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を使えばある程度回避することができます。

続きを読む

2012-05-20

ErlangとInverse Fizzbuzz

Inverse FizzbuzzErlangでやってみることにします。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)

それぞれ下記のようになりました。

続きを読む

2012-03-31

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入れようとしてやっと気付いた有様です。