[翻訳] Elixirのプロセスを探検する

Michael Kohlさんの2014年12月16日付のブログ記事[Exploring Elixir processes](http://blog.oozou.com/exploring-elixir-processes/)の翻訳です。象さんがかわいい…。

[以前Elixirのプロセスについて翻訳したのですが](http://qiita.com/HirofumiTamori/items/a3ee8eaeca76b43ae614)、それを自分の[サンプル](http://qiita.com/HirofumiTamori/items/7936ed9c6f73f4c9b475)に適用しようとしました。というのもあの実装、どこかでエラーが起こると全体が止まっちゃうんで。ところがさてやってみようとしてもよくわからずにどうしたものかと悩んでいました。そこでさらに例を探してこの記事を見つけました。訳しながら勉強します。この記事と[次のOTPに関する記事](http://blog.oozou.com/an-intro-to-otp-in-elixir/)を合わせれば何とか理解できるかなあ…

それにしても**Elixir**は**REPL**である**iex**の出来がいいのでこういうのを試すのも簡単でいいですね。

------

**Elixir** 1.0がリリースされてから3ヶ月近く[^1]経ちましたが私達はまだ[この言語についてとても興奮しています](http://blog.oozou.com/why-we-are-excited-about-elixir/)。

[^1]: Elixir1.0のリリースは2014年9月18日)

このブログのポストで以前投稿した**FizzBuzz**を拡張してサーバーにしてみます。

## FizzBuzzサーバー

```ex
defmodule FizzBuzz do
  @moduledoc "A very simple FizzBuzz server"

  @doc """
    Computes and outputs the FizzBuzz value for the
    provided argument.
  """
  @spec compute(Integer) :: :ok
  def compute do
    receive do
      n -> IO.puts "#{n}: #{compute(n)}"
    end
    compute
  end

  defp compute(n) do
    case {rem(n, 3), rem(n, 5)} do
      {0, 0} -> :FizzBuzz
      {0, _} -> :Fizz
      {_, 0} -> :Buzz
      _      -> n
    end
  end
end
```

これはいくつかのドキュメンテーションとcompute/0の[typespec](http://elixir-lang.org/docs/stable/elixir/Kernel.Typespec.html)を含むシンプルなモジュールで`FizzBuzz`モジュールのエントリポイントとして動作します。プロセスのメールボックスから次の番号を取り出し、(プライベートなcompute/1関数を介して)計算、そして自分自身を次のメッセージ待ち受けのために呼び出してから結果を出力するのに[receive/1](http://elixir-lang.org/docs/stable/elixir/Kernel.SpecialForms.html#receive/1)を使っています。

## サーバーとの通信

新しく生成したこのサーバーを使うためにまず[spawn/3](http://elixir-lang.org/docs/stable/elixir/Kernel.html#spawn/3)を使って起動します。spawn/3はモジュール名、呼び出される関数名、及び関数への引数を引数に取ります。spawn/3の戻り値は新しく生成された[process](http://elixir-lang.org/getting_started/11.html)のPIDで変数fbに格納されます(Elixirでは関数の引数を囲むかっこはあってもなくてもよいことに注意)。

```ex
fb = spawn FizzBuzz, :compute, []
```

サーバーへメッセージを送るには[send/2](http://elixir-lang.org/docs/stable/elixir/Kernel.html#send/2)が使えます。

```ex
send fb, 3
```

この結果、"3:Fizz"が出力され、値として3が返されます。ではいくつかのメッセージを送ってみてcompute/0の中で再帰呼び出しが期待通りに動作するか確かめましょう。

```ex
10..15 |> Enum.map(&send(fb, &1))
```

ここでは**Elixir**のパイプ演算子([|>/2](http://elixir-lang.org/docs/stable/elixir/Kernel.html#|>/2))で10から15までの数の[Range](http://elixir-lang.org/docs/stable/elixir/Range)を[Enum.map/2](http://elixir-lang.org/docs/stable/elixir/Enum.html#map/2)に送ります。Enum.map/2はsend/2の部分適用バージョンを無名関数の引数(&1)とともに使います。結果は期待通り以下のようになりました：

```bash
10: Buzz
11: 11
12: Fizz
13: 13
14: 14
15: FizzBuzz
```

## エラーに対処する

ここで、観察力の鋭い読者諸兄はもし整数ではなくそれ以外の型のデータのが送られたらどうなるかと心配されていると思います。やってみましょう：

```iex
send fb, 1.0
```

このために次のようなエラーメッセージが出ます：

```iex
14:25:32.742 [error] Error in process <0.95.0> with exit value: {badarith,[{'Elixir.FizzBuzz',compute,1,[{file,"fizzbuzz.ex"},{line,10}]},{'Elixir.FizzBuzz',compute,0,[{file,"fizzbuzz.ex"},{line,4}]}]}
```

これはFizzBuzzサーバーがクラッシュしたことを意味します。しかしリンクしていないプロセスをあつかっているので、メインプログラム(この場合はiexのセッション)は影響を受けません。ではまたサーバーを起動しましょう。ただし今度は[spawn_link/3](http://elixir-lang.org/docs/stable/elixir/Kernel.html#spawn_link/3)を使って[リンクされたプロセス](http://elixir-lang.org/getting_started/11.html#11.3-links)として起動します：

```iex
fb = spawn_link FizzBuzz, :compute, []
```

これでFizzBuzzサーバー内で起きた問題は呼び出し元に伝搬し、メインプロセスを停止させます。

```iex
14:33:40.426 [error] Error in process <0.119.0> with exit value: {badarith,[{'Elixir.FizzBuzz',compute,1,[{file,"fizzbuzz.ex"},{line,10}]},{'Elixir.FizzBuzz',compute,0,[{file,"fizzbuzz.ex"},{line,4}]}]}


** (EXIT from #PID<0.116.0>) an exception was raised:
** (ArithmeticError) bad argument in arithmetic expression
fizzbuzz.ex:10: FizzBuzz.compute/1
fizzbuzz.ex:4: FizzBuzz.compute/0

```

FizzBuzzモデルのcompute/0にガード節を追加することでこのエラーは交通整理できます。このように：

```ex
def compute do
  receive do
    n when is_integer(n) -> IO.puts "#{n}: #{compute(n)}"
    _ -> IO.puts "Invalid argument"
  end
  compute
end
```

これはうまく動作する一方で**Erlang**の最も重要な原理である「クラッシュさせちまえ」に反するものです。防衛的にプログラミングする代わりに、エラーがプロセスを終了させて(もしあれば)モニタリングしているプロセスにお任せしするのがこの状況への対応になります。

## モニタリング

この状況への対応のひとつとしてモニターを設定するやり方があります。リンクとは異なり、これはプロセス間の一方向性の関係です。モニターを生成するために、FizzBuzzサーバーを[spawn_monitor/3](http://elixir-lang.org/docs/stable/elixir/Kernel.html#spawn_monitor/3)で起動します。spawn_monitor/3はPIDに加えてモニターへの参照を返します。以下の例ではこの参照は使いませんが手動でモニターを無効にする[demonitor/2](http://elixir-lang.org/docs/stable/elixir/Process.html#demonitor/2)を呼び出すのに役立ちます。

サーバーが死んだ場合にはメッセージがモニタリングしているプロセスに送られるようになります：

```iex
{fb, ref} = spawn_monitor FizzBuzz, :compute, []
send fb, 1.0
```

receive/1をiexセッションのメッセージレシーバーをセットアップするのに使っていないので手動で[flush/0](http://elixir-lang.org/docs/master/iex/IEx.Helpers.html#flush/0)ヘルパーを呼び出してモニタリングアラートを見る必要があります：

```iex
{:DOWN, #Reference<0.0.0.262>, :process, #PID<0.79.0>,
  {:badarith,
    [{FizzBuzz, :compute, 1, [file: 'fizzbuzz.ex', line: 16]},
    {FizzBuzz, :compute, 0, [file: 'fizzbuzz.ex', line: 10]}]}}
```

## 次は？

**Elixir**でプロセスを扱うのはとても簡単です。しかしFizzBuzzサーバーをスクラッチから実装したので**Elixir**の下まわりを支えている**Erlang/OTP**のパワーをほとんど使っていません。なのでこのシリーズの次回はまたFizzBuzzサーバーです。しかし今度は[gen_server](http://www.erlang.org/doc/man/gen_server.html)と[supervisor](http://www.erlang.org/doc/man/supervisor.html)**OTP**ビヘイビアを使います。チャンネルはそのまま！