Elixir入門 14: モジュールの属性
Tags: elixir, webdev, tutorial, programming
Published: 2018-12-18 01:41:03

---
title: Elixir入門 14: モジュールの属性
published: true
description: Elixir公式サイトの許諾を得て「Module attributes」の解説にもとづき、加筆補正を加えて、Elixirにおけるモジュール属性の使い方をご説明します。使う目的はつぎの3つです。
tags: #elixir, #webdev, #tutorial, #programming
---

本稿はElixir公式サイトの許諾を得て「[Module attributes](https://elixir-lang.org/getting-started/module-attributes.html)」の解説にもとづき、加筆補正を加えて、Elixirにおけるモジュール属性の使い方をご説明します。使う目的はつぎの3つです。

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


# 注釈として
Elixirはモジュール属性の考え方ををErlangから採り入れました。たとえば、[`@vsn`](https://hexdocs.pm/elixir/Module.html#module-vsn)はモジュールのバージョンを明らかにする属性です。`@vsn`はErlang VMがコードリロードの仕組みの中で用い、モジュールが更新されたかどうか確かめます。バージョンが示されなければ、そのバージョンがモジュール関数の[MD5](https://ja.wikipedia.org/wiki/MD5)チェックサムに設定されるのです。

```elixir
defmodule MyServer do
  @vsn 2
end
```

Elixirには予約されている属性がいくつかあります。よく使われる属性は、つぎのとおりです。

[`@moduledoc`](https://hexdocs.pm/elixir/Module.html#module-moduledoc): 現在のモジュールのドキュメントを定めます。
[`@doc`](https://hexdocs.pm/elixir/Module.html#module-doc-and-since): 関数やマクロのドキュメントを定めます。
[`@behaviour`](https://hexdocs.pm/elixir/Module.html#module-behaviour-notice-the-british-spelling): [OTP](http://erlang.org/doc/design_principles/des_princ.html)(「[Open Telecom Platform](https://en.wikipedia.org/wiki/Open_Telecom_Platform)」)またはユーザー定義のビヘイビアを指定します。
[`@before_compile`](https://hexdocs.pm/elixir/Module.html#module-before_compile): モジュールがコンパイルされる前に呼び出されます。コンパイル直前に、関数をモジュール内に差し込むことができます。

`@moduledoc`と`@doc`は、もっとも使われる属性です(「[Module Attributes](https://hexdocs.pm/elixir/writing-documentation.html#module-attributes)」参照)。Elixirではドキュメントが重視され、さまざまな機能によって参照できます。

たとえばつぎのように、モジュールにドキュメントを定めましょう。Elixirでは[ヒアドキュメント](https://ja.wikipedia.org/wiki/%E3%83%92%E3%82%A2%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88)を用いて、読みやすいドキュメントが書けます。属性に続けて、3つのダブルクォーテーションでテキストを囲んでください。複数行のテキストにMarkdownで書式を定められます。コンパイルしたモジュールのドキュメントは、IExから参照できるのです。

```elixir
defmodule Example do
  @moduledoc """
  あいさつのモジュールです。
  """

  @doc """
  "hello, "のあとに`name`が加えられた文字列を返します。

  ## Examples

      iex> Example.greeting("world")
      "hello, world"

  """
  def greeting(name), do: "hello, #{name}"
end
```

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

```terminal
$ elixirc example.exs
$ iex
```

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

#### 図001■IExのシェルに表示されたドキュメント
![elixir_14_001.png](https://thepracticaldev.s3.amazonaws.com/i/lpsk1mke12jw6uom1s3g.png)

ドキュメントからHTMLページを生成するツール[ExDoc](https://github.com/elixir-lang/ex_doc#exdoc)も用意されています。そのほかにサポートされている属性については「[Module](https://hexdocs.pm/elixir/Module.html)」をご参照ください。また、属性は[Typespec](https://elixir-lang.org/getting-started/typespecs-and-behaviours.html)を定めたり、開発者が使うこともあり、ライブラリがカスタムビヘイビアに拡張して用いる場合もあります。


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

```elixir
defmodule Example do
  @greeting "hello"
  def greeting(name), do: "#{@greeting}, #{name}"
end
```

```elixir
iex> Example.greeting("world")
"hello, world"
```

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

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

```elixir
defmodule Example do
  @greeting
end
```

```terminal
warning: undefined module attribute @greeting, please remove access to @greeting or explic
itly set it before access
  example.exs: Example (module)
```

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

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

```elixir
defmodule Example do
  @greeting "hello"
  def greeting(name), do: "#{@greeting}, #{name}"
  @greeting "こんにちは"
  def greeting_jp(name), do: "#{@greeting}、#{name}"
end
```

```elixir
iex> Example.greeting("world")
"hello, world"
iex> Example.greeting_jp("日本")
"こんにちは、日本"
```


# 一時的な保存場所として
Elixir開発のプロジェクトのひとつに[`Plug`](https://github.com/elixir-lang/plug)があります。webライブラリやフレームワークを構築するための基盤となるプロジェクトです。`Plug`ライブラリを使うと、開発者はwebサーバーで動く独自の`Plug`が定められます。開発者がDSLをつくるとき、`Plug`はモジュール属性を用います。

つぎのコードの抜書きでは、[`plug/2`マクロ](https://hexdocs.pm/plug/Plug.Builder.html#plug/2)を使って、webリクエストがあったときに呼び出す関数を接続しています(第2引数はデフォルト値`[]`)。内部的には、`plug/2`を呼び出すたびに、`Plug`ライブラリにより引数は`@plugs`属性に納められるのです。そして、モジュールがコンパイルされる直前に、`Plug`はコールバックを呼び出します。コールバックとして定められるのは、HTTPリクエストを扱う[`call/2`](https://hexdocs.pm/plug/Plug.html#c:call/2)です。この関数が`@plugs`の中のすべての`Plug`を順に実行します。


```elixir
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`フレームワーク](https://hexdocs.pm/ex_unit/ExUnit.html)です。属性が注釈および保存場所として用いられます。

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

```elixir
defmodule MyTest do
  use ExUnit.Case

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


#### Elixir入門もくじ
- [Elixir入門 01: コードを書いて試してみる](https://dev.to/gumi/elixir-01--2585)
- [Elixir入門 02: 型の基本](https://dev.to/gumi/elixir-02--30n1)
- [Elixir入門 03: 演算子の基本](https://dev.to/gumi/elixir-03--33im)
- [Elixir入門 04: パターンマッチング](https://dev.to/gumi/elixir-04--1346)
- [Elixir入門 05: 条件 - case/cond/if](https://dev.to/gumi/elixir-05----casecondif-60o)
- [Elixir入門 06: バイナリと文字列および文字リスト](https://dev.to/gumi/elixir-06--35na)
- [Elixir入門 07: キーワードリストとマップ](https://dev.to/gumi/elixir-07--39hi)
- [Elixir入門 08: モジュールと関数](https://dev.to/gumi/elixir-08--1c4c)
- [Elixir入門 09: 再帰](https://dev.to/gumi/elixir-09--1a0p)
- [Elixir入門 10: EnumとStream](https://dev.to/gumi/elixir-10-enumstream-4fpb)
- [Elixir入門 11: プロセス](https://dev.to/gumi/elixir-11--2mia)
- [Elixir入門 12: 入出力とファイルシステム](https://dev.to/gumi/elixir-12--4og6)
- [Elixir入門 13: aliasとrequireおよびimport](https://dev.to/gumi/elixir-13-aliasrequireimport-55c1)
- Elixir入門 14: モジュールの属性
- [Elixir入門 15: 構造体](https://dev.to/gumi/elixir-15--4f43)
- [Elixir入門 16: プロトコル](https://dev.to/gumi/elixir-16--lif)
- [Elixir入門 17: 内包表記](https://dev.to/gumi/elixir-17--5gci)
- [Elixir入門 18: シギル](https://dev.to/gumi/elixir-18--5791)
- [Elixir入門 19: tryとcatchおよびrescue](https://dev.to/gumi/elixir-19-trycatchrescue-50i8)
- [Elixir入門 20: 型の仕様とビヘイビア](https://dev.to/gumi/elixir-20--j50)
- [Elixir入門 21: デバッグ](https://dev.to/gumi/elixir-21--21a1)
- [Elixir入門 22: Erlangライブラリ](https://dev.to/gumi/elixir-22-erlang-2492)
- [Elixir入門 23: つぎのステップ](https://dev.to/gumi/elixir-23--50ik)

##### 番外
- [Elixir入門: Plugを使うには](https://dev.to/gumi/elixir-plug-40lb)