DEV Community

gumi TECH for gumi TECH Blog

Posted on • Edited on

Elixir入門 14: モジュールの属性

本稿はElixir公式サイトの許諾を得て「Module attributes」の解説にもとづき、加筆補正を加えて、Elixirにおけるモジュール属性の使い方をご説明します。使う目的はつぎの3つです。

  • モジュールの注釈
    • ユーザーやVMが情報を加えます。
  • モジュールの定数
  • モジュールの一時的な保存場所
    • コンパイル時の保管に使われます。

注釈として

Elixirはモジュール属性の考え方ををErlangから採り入れました。たとえば、@vsnはモジュールのバージョンを明らかにする属性です。@vsnはErlang VMがコードリロードの仕組みの中で用い、モジュールが更新されたかどうか確かめます。バージョンが示されなければ、そのバージョンがモジュール関数のMD5チェックサムに設定されるのです。

defmodule MyServer do
  @vsn 2
end
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

コマンドラインツールでelixircコマンドによりファイルをコンパイルし、iexモードに入ります。

$ elixirc example.exs
$ iex
Enter fullscreen mode Exit fullscreen mode

hのあとにモジュールあるいは完全修飾名の関数を入力すれば、シェルにドキュメントが示されます(図001)。

図001■IExのシェルに表示されたドキュメント

elixir_14_001.png

ドキュメントからHTMLページを生成するツールExDocも用意されています。そのほかにサポートされている属性については「Module」をご参照ください。また、属性はTypespecを定めたり、開発者が使うこともあり、ライブラリがカスタムビヘイビアに拡張して用いる場合もあります。

定数として

Elixirにおける開発ではモジュール属性は、モジュールの定数としてよく用いられます。

defmodule Example do
  @greeting "hello"
  def greeting(name), do: "#{@greeting}, #{name}"
end
Enter fullscreen mode Exit fullscreen mode
iex> Example.greeting("world")
"hello, world"
Enter fullscreen mode Exit fullscreen mode

Erlangとは異なり、ユーザーの定めた属性はデフォルトではモジュールには納められません。コンパイルの間だけ存在する値です。Erlangと同じようなふるまいにするには、Module.register_attribute/3で属性を登録してください。

属性に値が与ないと、参照を除くか値を与えるよう警告が示されます。

defmodule Example do
  @greeting
end
Enter fullscreen mode Exit fullscreen mode
warning: undefined module attribute @greeting, please remove access to @greeting or explic
itly set it before access
  example.exs: Example (module)
Enter fullscreen mode Exit fullscreen mode

属性は関数本体が読み込むたびに、値のスナップショットがとられます。値は実行時ではなく、コンパイルのときに読まれるということです。後述のとおり、属性はモジュールをコンパイルする間、値の保存場所としても役立てられます。

与えられた属性の値に対して、関数は呼び出されます。なお、属性を定めるとき、値との間に改行を入れてはいけません。

defmodule Example do
  @greeting "hello"
  def greeting(name), do: "#{@greeting}, #{name}"
  @greeting "こんにちは"
  def greeting_jp(name), do: "#{@greeting}#{name}"
end
Enter fullscreen mode Exit fullscreen mode
iex> Example.greeting("world")
"hello, world"
iex> Example.greeting_jp("日本")
"こんにちは、日本"
Enter fullscreen mode Exit fullscreen mode

一時的な保存場所として

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, []
Enter fullscreen mode Exit fullscreen mode

モジュール属性を使ったもうひとつの例はExUnitフレームワークです。属性が注釈および保存場所として用いられます。

ExUnitではタグがテストの注釈として使われます。タグにより、あとでテストをフィルタリングできるのです。たとえば、外部テストが遅く、他のサービスに依存している場合があります。そうしたとき、手もとのマシンではやらずに、ビルドシステムで実行するといったことができるのです。

defmodule MyTest do
  use ExUnit.Case

  @tag :external
  test "contacts external service" do
    # ...
  end
end
Enter fullscreen mode Exit fullscreen mode

Elixir入門もくじ

番外

Top comments (1)

Collapse
 
arnaudmorisset profile image
Arnaud Morisset • Edited

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. :)