Erlang：プロセス間の並行プラミング入門１


Erlangでプロセス間の並行プログラミングを動かしてみました。
MacBook環境を使いました。
MacBookには、以下の記事でElixirを入れた際に、自動的に入るErlangを使いました。

###__（ 関連記事 ）__

https://qiita.com/electronics_diy721/items/6ff0c60d32ab0a9cbc5c

###__ErlangとIExインタプリンタ環境のバージョン__

- __Eshell__ : V12.1.3
- __IEx__ : 1.12.3

```bash: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 ~ %
```

```bash: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 ~ %
```


###__（ 参考 ）__

http://erlang.org/faq/getting_started.html

- Fred Hebert（著）・山口能迪（訳）『すごいErlangゆかいに学ぼう！』（Ohmsha）         

ターミナルで__*erl*__と打ち込んで、Erlangの対話型シェルであるIExを立ち上げます。
まずは、整数と実数の数値演算と、値の等価性比較をしてみます。

```erlang: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>
```

次に、パターンマッチです。

```erlang: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}
```

- ２つの変数XとYは、すでに定義済み（{X, Y}を{4, 5}とパターンマッチした際に、Xを4に、Yを5に束縛済み）のため、後から、Xを1に、Yを2に上書き再定義__（二重束縛）__は__できない__。
- 左辺と右辺のパターンマッチは成立しない（左辺と右辺は等価ではない）
- 変数がどこかで上書き変更されてしまうことが、言語仕様レベルで禁止されている。なので、ある変数を利用するコード（関数）は、常に安心して、その変数を引数として受け入れる（その変数に関数を適用させる）ことができる（__参照透過性__と__値不変性__の組み合わせによる__安全性__）


```erlang:IEx
18> X.
4
19> Y.
5
20> 
```

- ２つの変数XとYは、4と5のまま（__値不変性__）。

```erlang: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から抜ける。

##スクリプトファイルをコンパイルして動かしてみる

```bash: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*

```erlang:hello.erl
-module(hello).
-export([hello_world/0]).

hello_world() -> io:fwrite("hello, world\n").
```

- IExインタプリタ環境で、__*c( )*__を実行してスクリプトファイルをコンパイル
- __モジュール名：関数名（ ）__で、コンパイルした関数を実行

```erlang: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*）

```erlang:calc.erl
-module(calc).
-export([add/2, mul/2]).
add(A,B) -> A+B.
mul(A,B) -> A*B.

```

- IEx環境の中でコンパイルして、実行する。

```erlang: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

```erlang: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の強み（使い所）の１つであるプロセス間の並行プログラミング（の触り）を試してみます。__

- spwanを実行するたびに、新たなプロセスが生成される。
- そのため、標準出力には、その都度、新しいプロセスのプロセスID（PID）が表示される。

ここからは、以下の教科書所収のコードを写経して叩いていきます。

- Fred Hebert（著）・山口能迪（訳）『すごいErlangゆかいに学ぼう！』（Ohmsha）  

```erlang: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*を定義する。
- 各引数に対応する返り値を定義。


```erlang: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.

```


```erlang: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関数で新しいプロセスを立ち上げ直す必要がある。

```erlang:IEx
5> Dolphin = spawn(dolphins, dolphin1, []).
** exception error: no match of right hand side value <0.90.0>
```

- Dolphin変数はすでに定義済み（spwan(dolphins, dolphin1, [])に束縛済みなので、二重束縛はできない。
- 左辺と右辺のパターンマッチは成立しない（左辺と右辺は等価ではない）


```erlang: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を受け取った場合を除いて、処理を実行後に、自分自身を再帰的に繰り返し実行します。その結果、処理を実行した後もこのモジュールを呼び出したプロセスは死なないで、新しい引数を待ち続ける状態で生き続けます。


```erlang: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.

```


```erlang: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> 
```


```erlang: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 % 
```