本稿はElixir公式サイトの許諾を得て「Module attributes」の解説にもとづき、加筆補正を加えて、Elixirにおけるモジュール属性の使い方をご説明します。使う目的はつぎの3つです。
- モジュールの注釈
- ユーザーやVMが情報を加えます。
- モジュールの定数
- モジュールの一時的な保存場所
- コンパイル時の保管に使われます。
注釈として
Elixirはモジュール属性の考え方ををErlangから採り入れました。たとえば、@vsn
はモジュールのバージョンを明らかにする属性です。@vsn
はErlang VMがコードリロードの仕組みの中で用い、モジュールが更新されたかどうか確かめます。バージョンが示されなければ、そのバージョンがモジュール関数のMD5チェックサムに設定されるのです。
defmodule MyServer do
@vsn 2
end
Elixirには予約されている属性がいくつかあります。よく使われる属性は、つぎのとおりです。
@moduledoc
: 現在のモジュールのドキュメントを定めます。
@doc
: 関数やマクロのドキュメントを定めます。
@behaviour
: OTP(「Open Telecom Platform」)またはユーザー定義のビヘイビアを指定します。
@before_compile
: モジュールがコンパイルされる前に呼び出されます。コンパイル直前に、関数をモジュール内に差し込むことができます。
@moduledoc
と@doc
は、もっとも使われる属性です(「Module Attributes」参照)。Elixirではドキュメントが重視され、さまざまな機能によって参照できます。
たとえばつぎのように、モジュールにドキュメントを定めましょう。Elixirではヒアドキュメントを用いて、読みやすいドキュメントが書けます。属性に続けて、3つのダブルクォーテーションでテキストを囲んでください。複数行のテキストにMarkdownで書式を定められます。コンパイルしたモジュールのドキュメントは、IExから参照できるのです。
defmodule Example do
@moduledoc """
あいさつのモジュールです。
"""
@doc """
"hello, "のあとに`name`が加えられた文字列を返します。
## Examples
iex> Example.greeting("world")
"hello, world"
"""
def greeting(name), do: "hello, #{name}"
end
コマンドラインツールでelixirc
コマンドによりファイルをコンパイルし、iex
モードに入ります。
$ elixirc example.exs
$ iex
h
のあとにモジュールあるいは完全修飾名の関数を入力すれば、シェルにドキュメントが示されます(図001)。
図001■IExのシェルに表示されたドキュメント
ドキュメントからHTMLページを生成するツールExDocも用意されています。そのほかにサポートされている属性については「Module」をご参照ください。また、属性はTypespecを定めたり、開発者が使うこともあり、ライブラリがカスタムビヘイビアに拡張して用いる場合もあります。
定数として
Elixirにおける開発ではモジュール属性は、モジュールの定数としてよく用いられます。
defmodule Example do
@greeting "hello"
def greeting(name), do: "#{@greeting}, #{name}"
end
iex> Example.greeting("world")
"hello, world"
Erlangとは異なり、ユーザーの定めた属性はデフォルトではモジュールには納められません。コンパイルの間だけ存在する値です。Erlangと同じようなふるまいにするには、Module.register_attribute/3
で属性を登録してください。
属性に値が与ないと、参照を除くか値を与えるよう警告が示されます。
defmodule Example do
@greeting
end
warning: undefined module attribute @greeting, please remove access to @greeting or explic
itly set it before access
example.exs: Example (module)
属性は関数本体が読み込むたびに、値のスナップショットがとられます。値は実行時ではなく、コンパイルのときに読まれるということです。後述のとおり、属性はモジュールをコンパイルする間、値の保存場所としても役立てられます。
与えられた属性の値に対して、関数は呼び出されます。なお、属性を定めるとき、値との間に改行を入れてはいけません。
defmodule Example do
@greeting "hello"
def greeting(name), do: "#{@greeting}, #{name}"
@greeting "こんにちは"
def greeting_jp(name), do: "#{@greeting}、#{name}"
end
iex> Example.greeting("world")
"hello, world"
iex> Example.greeting_jp("日本")
"こんにちは、日本"
一時的な保存場所として
Elixir開発のプロジェクトのひとつにPlug
があります。webライブラリやフレームワークを構築するための基盤となるプロジェクトです。Plug
ライブラリを使うと、開発者はwebサーバーで動く独自のPlug
が定められます。開発者がDSLをつくるとき、Plug
はモジュール属性を用います。
つぎのコードの抜書きでは、plug/2
マクロを使って、webリクエストがあったときに呼び出す関数を接続しています(第2引数はデフォルト値[]
)。内部的には、plug/2
を呼び出すたびに、Plug
ライブラリにより引数は@plugs
属性に納められるのです。そして、モジュールがコンパイルされる直前に、Plug
はコールバックを呼び出します。コールバックとして定められるのは、HTTPリクエストを扱うcall/2
です。この関数が@plugs
の中のすべての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, []
モジュール属性を使ったもうひとつの例はExUnit
フレームワークです。属性が注釈および保存場所として用いられます。
ExUnit
ではタグがテストの注釈として使われます。タグにより、あとでテストをフィルタリングできるのです。たとえば、外部テストが遅く、他のサービスに依存している場合があります。そうしたとき、手もとのマシンではやらずに、ビルドシステムで実行するといったことができるのです。
defmodule MyTest do
use ExUnit.Case
@tag :external
test "contacts external service" do
# ...
end
end
Elixir入門もくじ
- Elixir入門 01: コードを書いて試してみる
- Elixir入門 02: 型の基本
- Elixir入門 03: 演算子の基本
- Elixir入門 04: パターンマッチング
- Elixir入門 05: 条件 - case/cond/if
- Elixir入門 06: バイナリと文字列および文字リスト
- Elixir入門 07: キーワードリストとマップ
- Elixir入門 08: モジュールと関数
- Elixir入門 09: 再帰
- Elixir入門 10: EnumとStream
- Elixir入門 11: プロセス
- Elixir入門 12: 入出力とファイルシステム
- Elixir入門 13: aliasとrequireおよびimport
- Elixir入門 14: モジュールの属性
- Elixir入門 15: 構造体
- Elixir入門 16: プロトコル
- Elixir入門 17: 内包表記
- Elixir入門 18: シギル
- Elixir入門 19: tryとcatchおよびrescue
- Elixir入門 20: 型の仕様とビヘイビア
- Elixir入門 21: デバッグ
- Elixir入門 22: Erlangライブラリ
- Elixir入門 23: つぎのステップ
Top comments (1)
I'm sure that the developers at Gumi have a lot of interesting things to say related to the back-end behind the mobile games of the company.
However, choosing Japanese over English doesn't help the sharing of knowledge here. :)