Elixir

ElixirのGETTING STARTED(14. Module attributes)をやってみた

URL

http://elixir-lang.org/getting-started/processes.html

試した環境

  • Ubuntu Server 14.04 LTS
  • Erlang/OTP 18
  • Elixir 1.0.4

Module attributes

Elixirのモジュール属性は3つの目的を果たす.

* VMまたはユーザが使用する情報をなどモジュールに注釈をつける
* 定数として利用する
* コンパイル時にモジュールの一時的な保存場所として利用する

As annotations

Elixirのモジュール属性のコンセプトはErlangから持ってきたもの。
例えば、次のようなもの。

defmodule MyServer do
    @vsn 2
end

上記の例では、モジュールにバージョン属性を明示的に設定している。
@vsn はモジュールがアップデートされたかどうかをチェックするErlang VMのコードを再ロードするメカニズムによって使われる。

もし、バージョンを指定しない場合、モジュール関数のMD5チェックサムがバージョンに設定される。

Elixirは数個の予約済みの属性を持っている。ここではよく使われるものを少し紹介する。

  • @moduledoc - こののモジュールのためのドキュメントを提供する
  • @doc - この属性に続く関数もしくはマクロのドキュメントを提供する。
  • @behaviour - 利用するOTPもしくは、ユーザ定義のビヘイビアを指定する。(イギリス英語のスペルであることに注意)
  • @before_compile - モジュールがコンパイルされる前に呼び出されるフックを提供する。コンパイル直前にモジュール内に関数を追加することを可能にする。

@moduledoc@doc は最もよく使われる属性で、これらをよく使っていることを期待する。Elixirはドキュメントを一等として扱い、ドキュメントへアクセスできる多く関数を用意している。

Math モジュールで、いくつかのドキュメントを追加し、math.ex ファイルとして保存してみる

math.ex
defmodule Math do
  @moduledoc """
  Provides math-related functions.

  ## Examples

      iex> Math.sum(1, 2)
      3

  """

  @doc """
  Calculates the sum of two numbers.
  """
  def sum(a, b), do: a + b
end

Elixirは読みやすい文章をかくためにヒアドキュメントでのマークダウンの使用を促進している。
ヒアドキュメントは複数行の文字列で、開始と終了は3つのダブルクオートで、内部のテキストおフォーマットを維持する。iexからコンパイル済みのドキュメントにアクセスできる。

$ elixirc math.ex
$ iex
iex> h Math

                                      Math                                      

Provides math-related functions.

Examples

iex > Math.sum(1, 2) 3
iex(21)> h Math.sum

                                 def sum(a, b)                                  

Caluculates the sum of two numbers.

ドキュメントからHTMLページを生成するExDocと呼ばれるうツールが提供されている。

サポートしている属性の完全なリストをモジュールのドキュメントを見ることができる。Elixrは次のような属性でtypespecsの定義をする。

  • @spec - 関数の仕様を提供する
  • @callback - ビヘイビアのコールバックの仕様を提供する
  • @type - @spec で使ったtypeの定義をする
  • @typep -@spec```で使ったプライベートのtypeの定義をする
  • @opaque - @spec で使ったopaque typeの定義をする。

このセクションでは組み込みの属性をカバーした。しかし、属性は開発者もしくは拡張されたカスタムビヘイビアをサポートするためのライブラリによって使われる。

As constants

Elixir開発者は、よく定数としてモジュール属性を使う。

defmodule MySever do
  @initial_state %{host: "147.0.0.1", port: 3456}
  IO.inspect @initial_state
end

注意:Erlangとは違い、デフォルトではユーザ定義の属性はモジュール内に保存されない。コンパイル時にのみ値は存在する。開発者は Module.register_attribute/3 を呼び出すことで、属性をErlangに近い振る舞いに設定できる。

定義されていない属性にアクセスした場合は、ワーニングが発生する。

iex> defmodule MaySever do
...>    @unknown
...> end
iex:28: warning: undefined module attribute @unknown, please remove access to @unknown or explicitly set it to nil before access

最後に、属性は関数内部で読み出すことができる。

iex> defmodule MyServer do
...>   @my_data 14
...>   def first_data, do: @my_data
...>   @my_data 13
...>   def second_data, do: @my_data
...> end
{:module, MyServer,
 <<70, 79, 82, 49, 0, 0, 4, 208, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 134, 131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 95, 118, 49, 108, 0, 0, 0, 2, 104, 2, ...>>,
 {:second_data, 0}}
iex> MyServer.first_data
14
iex> MyServer.second_data
13

関数の内部で属性を読み出すことは、現在のスナップショットの値を取ることに注意する。言い換えると、値はコンパイルの時に読み込まれ、実行時に読み込まれるわけではない。この後みるように、属性によるストレージはコンパイルするときに便利。

As temporariy storage

Elixirのプロジェクトに1つに、Plugプロジェクトがある。
これは、ElixrでののWebライブラリとフレームワークの共通の基盤である。

Plugライブラリは、開発者に自身のWebサーバ内で実行されるplugを定義できるようにする。

defmodule MyPlug do
  use Plug.Builder

  plug :set_header
  plug :send_ok

  def set_header(conn, _opts) do
    put_resp_header(conn, "x-header", "set")
  end

  def send_ok(conn, _opts) do
    send(conn, 200, "ok")
  end
end

IO.puts "Running MyPlug with Cowboy on http://localhost:4000"
Plug.Adapters.Cowboy.http MyPlug, []

上記の例では、Webリクエストがあった場合に呼び出される関数に接続するためにplug/1 マクロを使った。内部的に、plug/1を呼び出す度に、Plugライブラリは与えられた引数を @plug 属性に保存する。モジュールがコンパイルされる直前に、Plugは定義されたhttpリクエストを取り扱うメソッド( call/2 )のコールバックする。このメソッド @plug にあるすべてのplugを順番に実行する。

基礎となるコードを理解するためには、マクロが必要。これについては、メタプログラミングガイドで再度触れる。ここでは、モジュール属性がストレージとしてどのように使われ、開発者にDSLを作成させることを許すのかに焦点を当てる。

注釈とストレージとしてモジュール属性使った他のサンプルとしてはExUnitフレームワークがある。

defmodule MyTest do
  use ExUnit.Case

  @tag :external
  test "contacts external service" do
    # ...
  end
end

ExUnitのタグはテストの注釈を付けるために使われる。タグは後でテストのフィルターとして使われる。例えば、遅かったり他のサービスに依存してたりする外部テストの実行するのは避けることができる。それによってビルドシステムを有効にすることができる。

Elixirがどうやってメタプログラミングをサポートしており、モジュールの属性がその重要な役割を担っているか、このセクションで多少なりとも理解できると良い。