URL
試した環境
- 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
ファイルとして保存してみる
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がどうやってメタプログラミングをサポートしており、モジュールの属性がその重要な役割を担っているか、このセクションで多少なりとも理解できると良い。