モジュール

和訳が古くなっている可能性があります。
いくつかの変更が翻訳元の文書に適用されています。

私たちは経験的に、全ての関数を1つの同じファイルとスコープに持つと手に負えないことを知っています。このレッスンでは関数をまとめ、構造体として知られる特別なマップを定義することで、コードをより効率のよい形に組織化する方法を取り上げます。

目次

モジュール

モジュールは関数群を名前空間へと組織する最良の方法です。関数をまとめることに加えて、関数のレッスンで取り上げた名前付き関数やプライベート関数を定義できます。

基本的な例を見てみましょう:

defmodule Example do
  def greeting(name) do
    "Hello #{name}."
  end
end

iex> Example.greeting "Sean"
"Hello Sean."

Elixirではモジュールをネストすることが可能で、機能ごとにさらなる名前空間をつけることができます:

defmodule Example.Greetings do
  def morning(name) do
    "Good morning #{name}."
  end

  def evening(name) do
    "Good night #{name}."
  end
end

iex> Example.Greetings.morning "Sean"
"Good morning Sean."

モジュールの属性

モジュール属性はElixirでは一般に定数として用いられることがほとんどです。単純な例を見てみましょう:

defmodule Example do
  @greeting "Hello"

  def greeting(name) do
    ~s(#{@greeting} #{name}.)
  end
end

重要なので言及しておきますと、 Elixir には予約されている属性があります。もっとも一般的なのは以下の3つです:

  • moduledoc — 現在のモジュールにドキュメントを付けます。
  • doc — 関数やマクロについてのドキュメント管理。
  • behaviour — OTP またはユーザが定義した振る舞い(ビヘイビア)に用います。

構造体

構造体は定義済みのキーの一群とデフォルト値を持つ特殊なマップです。モジュール内部で定義されなくてはならず、そのモジュールから名前をとります。構造体にとっては、モジュール内部で自身しか定義されていないというのもありふれたことです。

構造体を定義するには defstruct を用い、フィールドとデフォルト値のキーワードリストを添えます:

defmodule Example.User do
  defstruct name: "Sean", roles: []
end

いくつか構造体を作ってみましょう:

iex> %Example.User{}
%Example.User{name: "Sean", roles: []}

iex> %Example.User{name: "Steve"}
%Example.User{name: "Steve", roles: []}

iex> %Example.User{name: "Steve", roles: [:admin, :owner]}
%Example.User{name: "Steve", roles: [:admin, :owner]}

構造体はあたかもマップのように更新することができます:

iex> steve = %Example.User{name: "Steve", roles: [:admin, :owner]}
%Example.User{name: "Steve", roles: [:admin, :owner]}
iex> sean = %{steve | name: "Sean"}
%Example.User{name: "Sean", roles: [:admin, :owner]}

最も重要なことですが、構造体はマップに対してマッチすることができます:

iex> %{name: "Sean"} = sean
%Example.User{name: "Sean", roles: [:admin, :owner]}

コンポジション(Composition)

さて、モジュールと構造体の作り方がわかったので、コンポジションを用いてモジュールや構造体に既存の機能を追加する方法を学びましょう。 Elixir は他のモジュールと連携する様々な方法を用意しています。

alias

モジュール名をエイリアスすることができます。 Elixir のコードでは頻繁に使われます:

defmodule Sayings.Greetings do
  def basic(name), do: "Hi, #{name}"
end

defmodule Example do
  alias Sayings.Greetings

  def greeting(name), do: Greetings.basic(name)
end

# alias を使わない場合

defmodule Example do
  def greeting(name), do: Sayings.Greetings.basic(name)
end

2つのエイリアス間で衝突があったり、全体を別名でエイリアスしたい場合には、 :as オプションを使います:

defmodule Example do
  alias Sayings.Greetings, as: Hi

  def print_message(name), do: Hi.basic(name)
end

複数のモジュールを一度にエイリアスすることも可能です:

defmodule Example do
  alias Sayings.{Greetings, Farewells}
end

import

モジュールをエイリアスするよりも、関数やマクロを取り込みたいという場合には、 import を使います:

iex> last([1, 2, 3])
** (CompileError) iex:9: undefined function last/1
iex> import List
nil
iex> last([1, 2, 3])
3

フィルタリング

デフォルトでは全ての関数とマクロが取り込まれますが、 :only:except オプションを使うことでフィルタすることができます。

特定の関数やマクロを取り込むには、名前/アリティのペアを :only:except に渡す必要があります。 last/1 で最後の関数のみを取り込んでみましょう:

iex> import List, only: [last: 1]
iex> first([1, 2, 3])
** (CompileError) iex:13: undefined function first/1
iex> last([1, 2, 3])
3

last/1 で指定された関数以外を全て取り込むには、同じ関数で試してみましょう:

iex> import List, except: [last: 1]
nil
iex> first([1, 2, 3])
1
iex> last([1, 2, 3])
** (CompileError) iex:3: undefined function last/1

名前/アリティのペアに加えて、 :functions:macros という2つの特別なアトムもあります。これらはそれぞれ関数とマクロのみを取り込みます:

import List, only: :functions
import List, only: :macros

require

それほど頻繁に使われませんが、 require/2 も重要です。モジュールを require すると、コンパイルとロードが確実に行われます。モジュールのマクロを呼び出す必要がある場合には最も役に立ちます:

defmodule Example do
  require SuperMacros

  SuperMacros.do_stuff
end

まだロードされていないマクロを呼びだそうとすると、Elixir はエラーを発生させます。

use

use マクロを用いることで他のモジュールを利用して現在のモジュールの定義を変更することができます。 コード上で use を呼び出すと、実際には提供されたモジュールに定義されている __using__/1 コールバックを呼び出します。 __using__/1 マクロの結果はモジュールの定義の一部になります。 この動作に対する理解を深めるために簡単な例を見ましょう:

defmodule Hello do
  defmacro __using__(_opts) do
    quote do
      def hello(name), do: "Hi, #{name}"
    end
  end
end

ここでは hello/1 関数を定義する __using__/1 コールバックを定義した Hello モジュールを作りました。 この新しいコードを試すために新しいモジュールを作ります:

defmodule Example do
  use Hello
end

IExでこのコードを試して見ると Example モジュールで hello/1 を使えるのがわかります。

iex> Example.hello("Sean")
"Hi, Sean"

ここで useHello__using__/1 コールバックを呼び出して、結果のコードをモジュールに追加します。 基本的な例を見せたので、ここからはこのコードを変更して __using__/1 にオプションをサポートする方法を見てみましょう。 greeting オプションを追加します:

defmodule Hello do
  defmacro __using__(opts) do
    greeting = Keyword.get(opts, :greeting, "Hi")

    quote do
      def hello(name), do: unquote(greeting) <> ", " <> name
    end
  end
end

新しく作った greeting オプションを含むために Example モジュールを更新します:

defmodule Example do
  use Hello, greeting: "Hola"
end

IExで試して見ると挨拶が変わるのを確認できます。

iex> Example.hello("Sean")
"Hola, Sean"

これらは use がどうやって動作するのかを説明する簡単な例でしたが、これは Elixir のツールボックスで信じられないほどに強力なツールです。 Elixir を学び続けたら use をあっちこっちで見ることになるでしょう。かならず見ることになりそうな例をひとつあげれば、 use ExUnit.Case, async: true です。

注意: quotealiasuserequireメタプログラミングで使用してたマクロです。