Erlangのパターンマッチング

先日、「すでに"束縛されている変数"でパターンマッチングができる」と書いたとこにコメントをいただいたので補足。

Erlangでは、変数は1度しか束縛できない。
値を一度しか代入できない。
なので当然、変数Xに対して、次のように
まず1を代入、
次に2を代入、
としようとすると、エラーとなる。

50> X = 1.
1
51> X = 2.
=ERROR REPORT==== 19-Mar-2008::01:28:30 ===
Error in process <0.114.0> with exit value: {{badmatch,2},[{erl_eval,expr,3}]}
 ** exited: {{badmatch,2},[{erl_eval,expr,3}]} **

だけど、変数に、同じ値を代入すれば、2度目の代入でもエラーにならない。

45> X = 1.
1
46> X = 1.
1
47> X = 2 - 1.
1
48> X = 2.

=ERROR REPORT==== 19-Mar-2008::01:24:03 ===
Error in process <0.106.0> with exit value: {{badmatch,2},[{erl_eval,expr,3}]}
 ** exited: {{badmatch,2},[{erl_eval,expr,3}]} **

これは、Erlangの=が、よくある代入を意味するのではなく、
ある規則でパターンマッチングと束縛を行うからだ。
X = 1などとしたとき、
変数Xが、未束縛だったら、どんなものにもマッチして、その値に束縛される。
このとき当然パターンマッチに失敗することはない。
もし変数Xが、1に束縛済みだったら、
X = 1 は1 = 1と同じことになり、パターンマッチに成功する。
X = 2 は2 = 1となって、パターンマッチに失敗する。
さらに例を挙げると、X = 1と束縛されていて、Yが未束縛だったら、
{X, Y} = {2, 3}
だとマッチしないが、
{X, Y} = {1, 5}
だとマッチして、変数Yが値5に束縛される。
で、この仕組みが、receive文で便利に使えるもよう。

erlangでは、メッセージを受け取るためにreceive文がある。
receiveでは、関数の引数と同じようにパターンマッチをさせることができる。
で、パターンマッチの中で、関数の引数などのすでに束縛されている変数を使うことができる。以下例。

 -module(mod).
 -export([f/1]).

f(X) ->
    receive
        {X, Y} -> io:fwrite("ok ~w~n",[Y]);
        Any ->  io:fwrite("error ~w~n",[Any])
    end,
    f(X).

3> c(mod).
{ok,mod}
4> F1 = spawn(fun() -> mod:f(1) end).
<0.46.0>
5> F7 = spawn(fun() -> mod:f(7) end).
<0.48.0>
6> F1 ! {1, 99}.
ok 99
{1,99}
7> F1 ! {2, 99}.
error {2,99}
{2,99}
8> F1 ! {7, 99}.
error {7,99}
{7,99}
9> F7 ! {7, 99}.
ok 99
{7,99}
10> F7 ! {8, 99}.
error {8,99}
{8,99}

とか、

 -module(mod).
 -export([g/1]).

g(X) ->
    receive
        {set, Y} -> io:fwrite("set~n"),g(Y);
        X -> io:fwrite("yes~n"),g(X);
        Any -> io:fwrite("no~n"),g(X)
    end.
64> c(mod).                          
./mod.erl:15: Warning: variable 'Any' is unused
{ok,mod}
65> G = spawn(fun() -> mod:g(1) end).
<0.153.0>
66> G!1.
yes
1
67> G!2.
no
2
68> G!{set,2}.
set
{set,2}
69> G!1.      
no
1
70> G!2.      
yes
2

とか、

        
h() ->
    receive
        X -> io:fwrite("set~n")
    end,
    receive
        X -> io:fwrite("yes~n");
        Any -> io:fwrite("no~n")
    end,
    h().
 
79> H = spawn(fun mod:h/0).
<0.177.0>
80> H!1.
set
1
81> H!1.
yes
1
82> H!2.
set
2
83> H!3.
no
3
84> H!4.
set
4
85> H!4.
yes
4

とか

 -module(mod2).
 -export([get/1]).

get(X) ->
    receive
        {X, Y} -> Y
    end.
5> c(mod2).
{ok,mod2}
6> self()!{joe, "Hello!"}.
{joe,"Hello!"}
7> self()!{guido, "Python!"}.
{guido,"Python!"}
8> self()!{joe, "Erlang!"}.  
{joe,"Erlang!"}
9> self()!{guido, "GJ!"}.    
{guido,"GJ!"}
10> mod2:get(guido).
"Python!"
11> mod2:get(guido).
"GJ!"
12> mod2:get(joe).  
"Hello!"
13> mod2:get(joe).
"Erlang!"

のようなことができる。面白い。