[翻訳] ElixirにおけるOTPの紹介

前回に引き続き、Michael Kohlさんの2015年2月13日付のブログ記事[An intro to OTP in Elixir](http://blog.oozou.com/an-intro-to-otp-in-elixir/)の翻訳です。

-----

## OTPとは何か?

[ドキュメント](http://www.erlang.org/doc/)によればOTP－[Open Telecom Platform](https://en.wikipedia.org/wiki/Open_Telecom_Platform)－は「並行プログラミングのために完備された開発環境」で、**Erlang**コンパイラとインタプリタ、データベースサーバー([Mnesia](http://en.wikipedia.org/wiki/Mnesia))、解析ツール([Dyalizer](http://www.erlang.org/doc/man/dialyzer.html))それに多数のライブラリを含んでいます。人々がOTPについて話をするときに引き合いに出すのはこの後半の部分です。

## ビヘイビア(ふるまい) ##

[Erlang/OTPのデザイン原則](http://www.erlang.org/doc/design_principles/des_princ.html)の中心的なもののひとつはアプリケーションのパターン、OTP用語でいうところの「ビヘイビア」です。ビヘイビアは共通的なタスクに対する汎用的な実装を定義します。その一例として汎用サーバー([gen_server](http://elixir-lang.org/getting_started/mix_otp/3.html))モジュールがあります。アプリケーション開発者は実装固有のコードを特定の関数のセットとしてエクスポートされているコールバックモジュールに追加します。**Erlang**とは異なり**Elixir**はこれらの関数についてデフォルトの実装を提供しているので必要な実装をオーバーライドするだけですみます。

## GenServerを使ったFizzBuzzサーバー ##

理論は十分だ、コードを書きましょう！

新しくプロジェクトを作るのに[mix](http://elixir-lang.org/docs/stable/mix/)を使います。モジュール名は[キャメルケース(CamelCase)](https://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%83%A1%E3%83%AB%E3%82%B1%E3%83%BC%E3%82%B9)にしたいので`--module`オプションを与える必要があります。そうしないとモジュール名はデフォルトのFizzbuzzになります。

```bash
$ mix new fizzbuzz --module FizzBuzz
```

このコマンドはアプリケーションのテンプレートを一式生成しますが、このブログの投稿記事の目的のためには`lib/fizzbuzz.ex`についてだけ作業します。

ではアプリケーションに`GenServer` OTPビヘイビアを使うということを教えましょう：

```ex
use GenServer
require Logger
```

デバッグのために[Loggerモジュール]()も必要です。次にクライアントのためのAPIを定義します。

```ex
def start_link do
   GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end

def get(n) do
  GenServer.call(__MODULE__, {:print, n})
end

def print(n) do
  GenServer.cast(__MODULE__, {:print, n})
end
```

`FizzBuzz.start_link/0`はサーバーをリンクされたプロセスとして起動します。プロセスは[監視ツリー](http://elixir-lang.org/docs/stable/elixir/Supervisor.html)の一部となることもあります。サーバーを起動する時に`name`オプションを使いモジュール名(`__MODULE__`)という名前をつけたことに注意してください。この方法を使うと他の関数からサーバーを参照するのにPIDを使わずに済みます。

`FizzBuzz.get/1`と`FizzBuzz.print/1`はサーバーの主なインターフェースです。数値をひとつ引数として与えるとその数値に関連したFizzBuzzの値を出力します。GenServerは２種類のリクエストをサポートしています：[call](http://elixir-lang.org/docs/stable/elixir/GenServer.html#call/3)と[cast](http://elixir-lang.org/docs/stable/elixir/GenServer.html#cast/2)です。callは同期呼び出しで何かをクライアント(この例で言うとget関数)に返します。一方でcastは非同期呼び出しでクライアント(同じく、print関数)に値を返す必要はありません。

これはGenServerのインターフェースを個別のクライアントAPIの中でラップするのに一般的な方法なので覚えておいて損はありません。

これで面倒なことは片付いたので必要なGenServerのコールバック関数を実装しましょう。

```ex
def init(:ok) do
  Logger.debug "FizzBuzz server started"
  {:ok, %{}}
end

def handle_call({:print, n}, _from, state) do
  {:ok, fb, state} = fetch_or_calculate(n, state)
  {:reply, fb, state}
end

def handle_cast({:print, n}, state) do
  {:ok, fb, state} = fetch_or_calculate(n, state)
  IO.puts fb
  {:noreply, state}
end
```

`init/1`は`GenServer.start_link/3`から呼び出され`{:ok, state}`の形のタプルを返します。この例のケースではstate(状態)は単なる**Elixir**のマップ(%{})です。

`handle_call/2`と`handle_cast/2`はこのアプリケーションのコアです。第１引数をパターンマッチングで`{:print, n}`の形式のメッセージを処理する方法を指定します。プライベート関数`fetch_or_calculate/2`は値を取得、または計算します。そして適切な反応を返します。`call`の場合は`{:reply, response, state}`の形式、一方 `cast` は `{:noreply, state}`を返します。GenServerの慣習として[ドキュメント](http://elixir-lang.org/docs/stable/elixir/GenServer.html)は全てのリクエストのタイプについてあり得る全ての返り値のタイプをリストアップします。

これがプライベートヘルパー関数がどのように見えるかです。

```ex
defp fetch_or_calculate(n, state) do
  if Dict.has_key?(state, n) do
    Logger.debug "Fetching #{n}"
    {:ok, fb} = Dict.fetch(state, n)
  else
    Logger.debug "Calculating #{n}"
    fb = fizzbuzz(n)
    state = Dict.put(state, n, fb)
  end
  {:ok, fb , state}
end
```

`fetch_or_calculate/2`は数値nに対するFizzBuzzの値が既に計算されていたかどうかをチェックします。もし計算済みであったらそれを辞書から取得します。もしその値がまだ計算されていなかった場合は計算し、状態を新しく計算された値で(Dict.put(state, n, fb))することで更新します。最後にハンドラー関数でパターンマッチした結果に対する`{:okm value, state}`の形式のタプルを返します。

よし、これでFizzBuzzサーバーは準備完了です。以下に完成した全コードを示します：

```ex
defmodule FizzBuzz do
  use GenServer
  require Logger

  ## Client API

  def start_link do
     GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  def get(n) do
    GenServer.call(__MODULE__, {:print, n})
  end

  def print(n) do
    GenServer.cast(__MODULE__, {:print, n})
  end

  ## Server Callbacks

  def init(:ok) do
    Logger.debug "FizzBuzz server started"
    {:ok, %{}}
  end

  def handle_call({:print, n}, _from, state) do
    {:ok, fb, state} = fetch_or_calculate(n, state)
    {:reply, fb, state}
  end

  def handle_cast({:print, n}, state) do
    {:ok, fb, state} = fetch_or_calculate(n, state)
    IO.puts fb
    {:noreply, state}
  end

  defp fetch_or_calculate(n, state) do
    if Dict.has_key?(state, n) do
      Logger.debug "Fetching #{n}"
      {:ok, fb} = Dict.fetch(state, n)
    else
      Logger.debug "Calculating #{n}"
      fb = fizzbuzz(n)
      state = Dict.put(state, n, fb)
    end
    {:ok, fb , state}
  end

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

我々の書いたコードを使う時がきました。プロジェクトのディレクトリでアプリケーションのコンテキストでiexセッションを起動するために以下のコマンドを実行します。

```bash
$ iex -S mix
```

さて、いくつか値を与えてサーバーを起動しましょう。

```iex
FizzBuzz.start_link
4..6 |> Enum.map(&FizzBuzz.print/1)
```

以下はコマンドによって生成された出力です。リクエストに対して非同期で動作するので出力とログメッセージと関数の戻り値が入り混じっています：

```iex
4

10:54:58.026 [debug] Calculating 4
Buzz
[:ok, :ok, :ok]

10:54:58.028 [debug] Calculating 5
Fizz
```

別々のプロセスで走っているにも関わらず`IO.puts`の出力はiexのセッションで出てしまうのは、iexがここでは[グループリーダー](http://elixir-lang.org/getting-started/io-and-the-file-system.html#processes-and-group-leaders)になっているからです。

もし、既に計算済みの値を得ようとした(FizzBuzz.get(5))場合はその値が確かにキャッシュから取り出されていることがわかります：

```iex
10:58:56.774 [debug] Fetching 5
:Buzz
```

## まとめ ##

この投稿で、OTPを活用することでサーバーを実装するのがいかに簡単かを見てきました。我々が書いたFizzBuzzサーバーはより大きな**Elixir**のアプリケーションの一部としてデプロイ可能になりました。つまり監視とホットスワップという特長を持つに至ったわけです。

## リソース ##

* [OTP公式ドキュメント](http://www.erlang.org/doc/)
* [ElixirのOTPチュートリアル](http://elixir-lang.org/getting_started/mix_otp/1.html)
* [The Little Elixir and OTP Guidebook](http://www.exotpbook.com/)