Erlangでプロセス間の並行プログラミングを動かしてみました。
MacBook環境を使いました。
MacBookには、以下の記事でElixirを入れた際に、自動的に入るErlangを使いました。
( 関連記事 )
ErlangとIExインタプリンタ環境のバージョン
- Eshell : V12.1.3
- IEx : 1.12.3
Terminal
electron@diynoMacBook-Pro ~ % erl --version
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]
Eshell V12.1.3 (abort with ^G)
1> init:stop().
ok
2> %
electron@diynoMacBook-Pro ~ %
Terminal
electron@diynoMacBook-Pro ~ % iex --version
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]
IEx 1.12.3 (compiled with Erlang/OTP 24)
electron@diynoMacBook-Pro ~ %
( 参考 )
- Fred Hebert(著)・山口能迪(訳)『すごいErlangゆかいに学ぼう!』(Ohmsha)
ターミナルで__erl__と打ち込んで、Erlangの対話型シェルであるIExを立ち上げます。
まずは、整数と実数の数値演算と、値の等価性比較をしてみます。
IEx
electron@diynoMacBook-Pro ~ % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]
Eshell V12.1.3 (abort with ^G)
1> 5.0 =:= 5.
* 2:1: syntax error before: 5.0
1> 5.0 =:= 5.0.
true
2> 5.0 =/= 5.0.
false
3> 5.0 =/= 5.
true
4> 0 == false
4> 0 == false.
* 2:1: syntax error before: 0
4> 0 == false.
false
5> 0 == true.
false
6> 1 == true.
false
7> 1 < false.
true
8>
次に、パターンマッチです。
IEx
8> Point = {4, 5}.
{4,5}
9> Point.
{4,5}
10> {X, Y} = Point.
{4,5}
11> X.
4
12> Y.
5
13> {X, Y} = {1, 2}.
** exception error: no match of right hand side value {1,2}
14> X.
4
15> X.
4
16> Y.
5
17> Y.
5
18> {X, Y} = {10, 20}.
** exception error: no match of right hand side value {10,20}
- 2つの変数XとYは、すでに定義済み({X, Y}を{4, 5}とパターンマッチした際に、Xを4に、Yを5に束縛済み)のため、後から、Xを1に、Yを2に上書き再定義__(二重束縛)は__できない。
- 左辺と右辺のパターンマッチは成立しない(左辺と右辺は等価ではない)
- 変数がどこかで上書き変更されてしまうことが、言語仕様レベルで禁止されている。なので、ある変数を利用するコード(関数)は、常に安心して、その変数を引数として受け入れる(その変数に関数を適用させる)ことができる(参照透過性__と__値不変性__の組み合わせによる__安全性)
IEx
18> X.
4
19> Y.
5
20>
- 2つの変数XとYは、4と5のまま(値不変性)。
IEx
20> X = 3.
** exception error: no match of right hand side value 3
21> X.
4
22> PreciseTemperature = {Celsius, 21.213}.
* 1:23: variable 'Celsius' is unbound
23> PreciseTemperature = {celsius, 21.213}.
{celsius,21.213}
24> {kelvin, T} = PreciseTemperature.
** exception error: no match of right hand side value {celsius,21.213}
25> {celsius, 5.0} = PreciseTemperature.
** exception error: no match of right hand side value {celsius,21.213}
26> erlang:element(2, {a, b, c}).
b
27> erlang:element(1, {a, b, c}).
a
28> erlang:element(0, {a, b, c}).
** exception error: bad argument
in function element/2
called as element(0,{a,b,c})
*** argument 1: out of range
29> erlang:element(-10, {a, b, c}).
** exception error: bad argument
in function element/2
called as element(-10,{a,b,c})
*** argument 1: out of range
30> erlang:element(10, {a, b, c}).
** exception error: bad argument
in function element/2
called as element(10,{a,b,c})
*** argument 1: out of range
31>
32> lists:seq(1,4).
[1,2,3,4]
33> lists:seq(1, 4).
[1,2,3,4]
34> lists:seq(1, -4).
** exception error: no function clause matching lists:seq(1,-4) (lists.erl, line 243)
35> init:stop().
ok
36> %
electron@diynoMacBook-Pro ~ %
- init:stop( ).でIExから抜ける。
スクリプトファイルをコンパイルして動かしてみる
Terminal
electron@diynoMacBook-Pro ~ % mkdir erlang_training
electron@diynoMacBook-Pro ~ % cd erlang_training
electron@diynoMacBook-Pro erlang_training % vim hello.erl
"hello, world"を出力(副作用)するスクリプトファイル hello.erl
hello.erl
-module(hello).
-export([hello_world/0]).
hello_world() -> io:fwrite("hello, world\n").
- IExインタプリタ環境で、__c( )__を実行してスクリプトファイルをコンパイル
- __モジュール名:関数名( )__で、コンパイルした関数を実行
calc.erl
electron@diynoMacBook-Pro erlang_training % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]
Eshell V12.1.3 (abort with ^G)
1> c(hello).
{ok,hello}
2> hello:hello_world().
hello, world
ok
3> init:stop().
ok
4> %
electron@diynoMacBook-Pro erlang_training %
加算と乗算を定義したスクリプトファイルを用意(calc.erl)
calc.erl
-module(calc).
-export([add/2, mul/2]).
add(A,B) -> A+B.
mul(A,B) -> A*B.
- IEx環境の中でコンパイルして、実行する。
IEx
electron@diynoMacBook-Pro erlang_training % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]
Eshell V12.1.3 (abort with ^G)
1> c(calc).
{ok,calc}
2> calc:add(1,2).
3
3> calc:add(1, 2).
3
4>
- Erlangは、関数の引数の間にスペースを何文字入れてもOK
IEx
4> calc:add(1, 2).
3
5> calc:add(1 , 2).
3
6> calc:add( 1 , 2).
3
7> calc:add( 2 , 7).
9
8> calc:add(2, 7).
9
9> calc:add(2,7).
9
10> calc:mul(1, 7).
7
11> calc:mul(3, 7).
21
12> self().
<0.79.0>
13> self().
<0.79.0>
14> self().
<0.79.0>
15> F = fun() -> 2 + 2 end.
# Fun<erl_eval.45.65746770>
16> F
16> F.
* 2:1: syntax error before: F
16> spawn(F).
<0.102.0>
17> spawn(F).
<0.104.0>
18>
プロセス間通信をやってみる
Erlangの強み(使い所)の1つであるプロセス間の並行プログラミング(の触り)を試してみます。
- spwanを実行するたびに、新たなプロセスが生成される。
- そのため、標準出力には、その都度、新しいプロセスのプロセスID(PID)が表示される。
ここからは、以下の教科書所収のコードを写経して叩いていきます。
- Fred Hebert(著)・山口能迪(訳)『すごいErlangゆかいに学ぼう!』(Ohmsha)
IEx
18> spawn(F).
<0.106.0>
19> spawn(F).
<0.108.0>
20> spawn(F).
<0.110.0>
21> spawn(F).
<0.112.0>
29> H = fun(X) -> timer:sleep(10), io:format("~p~n", [X]) end.
# Fun<erl_eval.44.65746770>
30> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].
[<0.155.0>,<0.156.0>,<0.157.0>,<0.158.0>,<0.159.0>,
<0.160.0>,<0.161.0>,<0.162.0>,<0.163.0>,<0.164.0>]
1
2
3
4
5
6
7
8
9
10
31> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].
[<0.166.0>,<0.167.0>,<0.168.0>,<0.169.0>,<0.170.0>,
<0.171.0>,<0.172.0>,<0.173.0>,<0.174.0>,<0.175.0>]
1
2
3
4
5
6
7
8
9
10
32> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].
[<0.177.0>,<0.178.0>,<0.179.0>,<0.180.0>,<0.181.0>,
<0.182.0>,<0.183.0>,<0.184.0>,<0.185.0>,<0.186.0>]
1
2
3
4
5
6
7
8
9
10
33> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].
[<0.188.0>,<0.189.0>,<0.190.0>,<0.191.0>,<0.192.0>,
<0.193.0>,<0.194.0>,<0.195.0>,<0.196.0>,<0.197.0>]
1
2
3
4
5
6
7
8
9
10
44> [spawn(fun() -> H(X) end) || X <- lists:seq(1,10)].
[<0.219.0>,<0.220.0>,<0.221.0>,<0.222.0>,<0.223.0>,
<0.224.0>,<0.225.0>,<0.226.0>,<0.227.0>,<0.228.0>]
1
2
3
4
5
6
7
8
9
10
45> self() ! hello.
hello
46> self() ! ciao.
ciao
47> self() ! self() ! double
47> self() ! self() ! double.
* 2:1: syntax error before: self
47> self() ! self() ! double.
double
48> flush().
Shell got hello
Shell got ciao
Shell got double
Shell got double
ok
49> self() ! self() ! self() ! triple.
triple
50> flush().
Shell got triple
Shell got triple
Shell got triple
ok
51> self() ! self() ! self() ! triple2.
triple2
52> flush().
Shell got triple2
Shell got triple2
Shell got triple2
ok
53> self() ! self() ! self() ! triple3.
triple3
54> self() ! self() ! double2.
double2
55> self() ! self() ! self() ! triple4.
triple4
56> flush().
Shell got triple3
Shell got triple3
Shell got triple3
Shell got double2
Shell got double2
Shell got triple4
Shell got triple4
Shell got triple4
ok
57> init:stop().
ok
58> %
electron@diynoMacBook-Pro erlang_training %
- receiveで受け取った値の中身に応じて、異なる返り値を返す関数dolphin1を定義する。
- 各引数に対応する返り値を定義。
dolphins.erl
-module(dolphins).
-compile(export_all).
dolphin1() ->
receive
do_a_flip ->
io:format("How about no?~n");
fish ->
io:format("So long and thanks for all the fish!~n");
_ ->
io:format("Heh, we're smarter than you humans.~n")
end.
IEx
electron@diynoMacBook-Pro erlang_training % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]
Eshell V12.1.3 (abort with ^G)
1> c(dolphins).
dolphins.erl:2:2: Warning: export_all flag enabled - all functions will be exported
% 2| -compile(export_all).
% | ^
{ok,dolphins}
2>
2> Dolphin = spawn(dolphins, dolphin1, []).
<0.86.0>
3> Dolphin ! "oh, hello dolphins!".
Heh, we're smarter than you humans.
"oh, hello dolphins!"
4> Dolphin ! "Bon jour, Monsieur!".
"Bon jour, Monsieur!"
- 4>では、返り値として、__"oh, hello dolphins!"が__返されなかった。
- 3>で、Dolphinプロセスは仕事を終えて、死んでしまっているため。
- 新たに、spawn関数で新しいプロセスを立ち上げ直す必要がある。
IEx
5> Dolphin = spawn(dolphins, dolphin1, []).
** exception error: no match of right hand side value <0.90.0>
- Dolphin変数はすでに定義済み(spwan(dolphins, dolphin1, [])に束縛済みなので、二重束縛はできない。
- 左辺と右辺のパターンマッチは成立しない(左辺と右辺は等価ではない)
IEx
6> f(Dolphin).
ok
7> Dolphin = spawn(dolphins, dolphin1, []).
<0.95.0>
8> Dolphin ! "Bon jour, Monsieur!".
Heh, we're smarter than you humans.
"Bon jour, Monsieur!"
9> f(Dolphin).
ok
10> Dolphin = spawn(dolphins, dolphin1, []).
<0.99.0>
11> Dolphin ! "fish".
Heh, we're smarter than you humans.
"fish"
12> f(Dolphin).
ok
13> Dolphin = spawn(dolphins, dolphin1, []).
<0.103.0>
14> Dolphin ! fish.
So long and thanks for all the fish!
fish
15> f(Dolphin).
ok
16> Dolphin = spawn(dolphins, dolphin1, []).
<0.107.0>
17> Dolphin ! do_a_flip.
How about no?
do_a_flip
18> init:stop().
ok
19> % electron@diynoMacBook-Pro erlang_training %
教科書に掲載されている関数 delphin2を、dolphinsモジュールに追記します。
- dolphin2関数では、返り値を標準出力に表示(io:format)するのではなく、引数として受けとったPIDの持ち主であるプロセスに宛てて、返り値を送信(プロセス間通信)します。
- 但し、引数がdo_a_flipかfish以外の任意の変数を受け取った場合は、これまで通り、標準出力にメッセージを表示します。
- dolphin2関数は、fishを受け取った場合を除いて、処理を実行後に、自分自身を再帰的に繰り返し実行します。その結果、処理を実行した後もこのモジュールを呼び出したプロセスは死なないで、新しい引数を待ち続ける状態で生き続けます。
dolhins.erl
-module(dolphins).
-compile(export_all).
dolphin1() ->
receive
do_a_flip ->
io:format("How about no?~n");
fish ->
io:format("So long and thanks for all the fish!~n");
_ ->
io:format("Heh, we're smarter than you humans.~n")
end.
dolphin2() ->
receive
{From, do_a_flip} ->
From ! "How about no?";
{From, fish} ->
From ! "So long and thanks for all the fish!";
_ ->
io:format("Heh, we're smarter than you humans.~n")
end.
dolphin3() ->
receive
{From, do_a_flip} ->
From ! "How about no?",
dolphin3();
{From, fish} ->
From ! "So long and thanks for all the fish!";
_ ->
io:format("Heh, we're smarter than you humans.~n"),
dolphin3()
end.
IEx
electron@diynoMacBook-Pro erlang_training % erl
Erlang/OTP 24 [erts-12.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]
Eshell V12.1.3 (abort with ^G)
1>
2> c(dolphins).
dolphins.erl:2:2: Warning: export_all flag enabled - all functions will be exported
% 2| -compile(export_all).
% | ^
{ok,dolphins}
3> Dolphin2 = spawn(dolphins, dolphin2, []).
<0.86.0>
4> Dolphin2 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
5> flush().
Shell got "How about no?"
ok
6> Dolphin2 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
7> flush().
ok
8> Dolphin2 ! {self(), hi}.
{<0.88.0>,hi}
9> flush().
ok
10> f(Dolphin2).
ok
11> Dolphin2 = spawn(dolphins, dolphin2, []).
<0.97.0>
12> Dolphin2 ! {self(), hi}.
Heh, we're smarter than you humans.
{<0.88.0>,hi}
13> flush().
ok
14> f(Dolphin2).
ok
15> Dolphin2 = spawn(dolphins, dolphin2, []).
<0.102.0>
16> Dolphin2 ! {self(), fish}.
{<0.88.0>,fish}
17> flush().
Shell got "So long and thanks for all the fish!"
ok
18> f(Dolphin2).
ok
19>
IEx
36> Dolphin3 = spawn(dolphins, dolphin3, []).
<0.125.0>
37> Dolphin3 ! {self(), hi}.
Heh, we're smarter than you humans.
{<0.88.0>,hi}
38> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
39> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
40> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
41> flush().
Shell got "How about no?"
Shell got "How about no?"
Shell got "How about no?"
ok
42> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
43> Dolphin3 ! {self(), hello}.
Heh, we're smarter than you humans.
{<0.88.0>,hello}
44> Dolphin3 ! {self(), hello}.
Heh, we're smarter than you humans.
{<0.88.0>,hello}
45> Dolphin3 ! {self(), fish}.
{<0.88.0>,fish}
46> Dolphin3 ! {self(), do_a_flip}.
{<0.88.0>,do_a_flip}
47> flush().
Shell got "How about no?"
Shell got "So long and thanks for all the fish!"
ok
48> f(Dolphin3).
ok
49> init:stop().
ok
50> % electron@diynoMacBook-Pro erlang_training %