loading...
gumi TECH Blog

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

gumitech profile image gumi TECH Updated on ・3 min read

本稿は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のシェルに表示されたドキュメント

elixir_14_001.png

ドキュメントから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入門もくじ

番外

Posted on by:

gumitech profile

gumi TECH

@gumitech

gumi TECH は、株式会社gumiのエンジニアによる技術記事公開やDrinkupイベントなどの技術者交流を行うアカウントです。 gumi TECH Blog: http://dev.to/gumi / gumi TECH Drinkup: http://gumitech.connpass.com

gumi TECH Blog

株式会社gumiのエンジニアによる技術記事を公開しています。

Discussion

pic
Editor guide
 

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