Elixir入門 13: aliasとrequireおよびimport
gumi TECH
Posted on December 11, 2018
本稿はElixir公式サイトの許諾を得て「alias, require, and import」の解説にもとづき、加筆補正を加えて、Elixirのディレクティブalias
とrequire
およびimport
の使い方についてご説明します。
ソフトウェアが再利用しやすいように、Elixirはつぎの3つのディレクティブとひとつのマクロを備えています。ディレクティブはレキシカルスコープ(lexical scope)をもちます。use
は標準のマクロです。
-
alias
ディレクティブ: モジュールが別名で呼び出せるように別名を与えます。 -
require
ディレクティブ: マクロが呼び出せるようにモジュールを要求します。 -
import
ディレクティブ: モジュール名なしに関数が呼び出せるようにモジュールを読み込みます。 -
use
マクロ: モジュール内のコードを拡張ポイントとして呼び出します。
alias
alias/2
ディレクティブは、すでにあるモジュールに任意の別名を与えます。つぎのエイリアスが使われていないコードを例にとりましょう。
defmodule Sayings.Greetings do
def basic(name), do: "hello, #{name}"
end
defmodule Example do
def greeting(name), do: Sayings.Greetings.basic(name)
end
iex> Example.greeting("world")
"hello, world"
第2引数を省くと、ドット(.
)で区切られたモジュール名の最後の識別子がエイリアスになります。
defmodule Example do
alias Sayings.Greetings
def greeting(name), do: Greetings.basic(name)
end
第2引数で与える名前には、as:
オプションを添えてください。エイリアス名は大文字ではじめなければなりません。
defmodule Example do
alias Sayings.Greetings, as: Hi
def greeting(name), do: Hi.basic(name)
end
Elixirのモジュールはすべて名前空間Elixir
に定められます。デフォルトではそれが省けるということです。
iex> alias Example, as: String
Example
iex> String.greeting("tokyo")
"hello, tokyo"
iex> Elixir.String.length("hello")
5
エイリアスはレキシカルスコープをもちます。モジュールで定めれば、モジュール内の関数すべてがそのエイリアスを参照できます。
defmodule Example do
alias Sayings.Greetings, as: Hi
def greeting(name), do: Hi.basic(name)
def greeting_ex(name), do: Hi.basic(name) <> "!!"
end
iex> Example.greeting_ex("world")
"hello, world!!"
エイリアスを関数の中で定めると、他の関数からは参照できません。
defmodule Example do
def greeting(name) do
alias Sayings.Greetings, as: Hi
Hi.basic(name)
end
def greeting_ex(name), do: Hi.basic(name) <> "!!"
end
iex> Example.greeting("world")
"hello, world"
iex> Example.greeting_ex("world")
** (UndefinedFunctionError) function Hi.basic/1 is undefined (module Hi is not available)
Hi.basic("world")
example.exs: Example.greeting_ex/1
require
Elixirには、メタプログラミングの仕組みとしてマクロが備わっています。メタプログラミングとは、コードが生成されるコードを書くことです。マクロはコンパイルのとき展開されます。
パブリックの関数はグローバルに用いることができます。けれど、マクロを使うには、それが定義されたモジュールをrequire/2
ディレクティブでオプトインしなければなりません。
Integer.is_odd/1
はモジュールにマクロとして定められています(図001)。そのため、あらかじめInteger
モジュールをrequire/2
で設定しなければ使えません。
図001■リファレンスのInteger.is_odd/1の項にmacroの表示
iex> Integer.is_odd(5)
** (CompileError) iex: you must require Integer before invoking the macro Integer.is_odd/1
(elixir) src/elixir_dispatch.erl:97: :elixir_dispatch.dispatch_require/6
iex> require Integer
Integer
iex> Integer.is_odd(5)
true
-
Integer.is_even/1
: 奇数かどうかを論理値で返します。 -
Integer.is_odd/1
: 偶数かどうかを論理値で返します。
ディレクティブalias/2
と同じく、require/2
もレキシカルスコープをもちます。
import
import/2
は、完全修飾名を使わずに関数やマクロが参照できるディレクティブです。モジュール名なしに関数やマクロが呼び出せるようになります。
iex> import List
List
iex> first([1, 2, 3])
1
iex> last([1, 2, 3])
3
iex> flatten([1, [[2], 3]])
[1, 2, 3]
-
List.first/1
: リストの最初の要素を返します。 -
List.last/1
: リストの最後の要素を返します。
第2引数にonly:
オプションで、読み込む関数やマクロを絞り込めます。
iex> import List, only: [first: 1, last: 1]
List
iex> first([1, 2, 3])
1
iex> flatten([1, [[2], 3]])
** (CompileError) iex:9: undefined function flatten/1
第2引数のオプションには関数・マクロのリストのほか、:macros
と:functions
が与えられます。なお、import/2
で読み込まれるマクロは、内部的にrequire/2
も行わるのです。
iex> import Integer, only: :macros
Integer
iex> is_even(4)
true
iex> digits(123)
** (CompileError) iex: undefined function digits/1
iex> Integer.digits(123)
[1, 2, 3]
iex> import Integer, only: :functions
Integer
iex> digits(123)
[1, 2, 3]
iex> is_odd(3)
** (CompileError) iex: undefined function is_odd/1
第2引数のオプションとして、only:
の替わりにexcept:
で逆に読み込みから除くリストも定められます。
iex> import List, except: [first: 1, last: 1]
List
iex> flatten([1, [[2], 3]])
[1, 2, 3]
iex> last([1, 2, 3])
** (CompileError) iex: undefined function last/1
import/2
ディレクティブもレキシカルスコープです。関数の中に定めると、他の関数からは参照できず、コンパイルエラーになります。
defmodule Example do
def split(number) do
import Integer, only: [digits: 1]
digits(number)
end
def test(name), do: digits(name) #コンパイルエラー
end
use
use/2
マクロを使うと、モジュールの定めを他のモジュールから変えられるようになります。use/2
が呼び出すのは、モジュールに加えられた__using__/1
コールバックのマクロです。すると、このマクロの働きを別のモジュールに取り込めます。
つぎのコードが__using__/1
コールバックを定めたモジュールの例です。ここではテスト用に使うだけですので、マクロについて詳しくは「Macros」をお読みください。
defmodule Hello do
defmacro __using__(_opts) do
quote do
def greeting(name), do: "hello, #{name}"
end
end
end
別のモジュールでuse/2
を用いると、__using__/1
コールバックに定めた関数が、そのモジュールから呼び出せます。
defmodule Example do
use Hello
end
iex> Example.greeting("world")
"hello, world"
use/2
の機能は、つぎのようにrequire/2
を使ったのと同じです。
defmodule Example do
# use Hello
require Hello
Hello.__using__(greeting: :value)
end
さらに、__using__/1
コールバックをつぎのように書き替えます。すると、関数の処理が、use/2
の第2引数により変えられるのです。
defmodule Hello do
defmacro __using__(opts) do
hello = Keyword.get(opts, :hello, "hello")
quote do
def greeting(name), do: unquote(hello) <> "," <> name
end
end
end
defmodule Example do
use Hello, hello: "こんにちは"
end
iex> Example.greeting("日本")
"こんにちは,日本"
エイリアスを理解する
Elixirのエイリアスは、コンパイルするときアトムに変換される頭が大文字の識別子(String
やKeyword
など)です。
iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> String == :"Elixir.String"
true
alias/2
を用いると、エイリアスはアトムに展開されます。Erlang VM(およびElixir)では、モジュールはアトムで表されるからです。
iex> :lists.flatten([1, [[2], 3]])
[1, 2, 3]
モジュールを入れ子にする
モジュールは入れ子にすることができます。外から子のモジュールを参照するには、親からの完全修飾名を用いなければなりません。
defmodule Example do
def greeting(name), do: "hello, #{name}"
defmodule Greetings do
def morning(name), do: "good morning, #{name}"
end
end
iex> Example.greeting("world")
"hello, world"
iex> Example.Greetings.morning("tokyo")
"good morning, tokyo"
親モジュールの中に子のモジュールが定められたあとであれば、子は完全修飾名を使わずに参照できます。子モジュールが親のレキシカルスコープに含まれるため、内部的にエイリアスがつくられるからです。
defmodule Example do
def greeting(name), do: "hello, #{name}"
defmodule Greetings do
def morning(name), do: "good morning, #{name}"
end
# alias Example.Greetings #<- 内部的にエイリアスがつくられる
def call_child(name), do: Greetings.morning(name)
end
iex> Example.call_child("japan")
"good morning, japan"
完全修飾名を用いれば、子モジュールは親の外でも定められます。このとき、子モジュールは必ずしも親のあとに書かなくても構いません。このとき親モジュールが子の名前だけで参照したいときには、エイリアスを使ってください。
defmodule Example do
def greeting(name), do: "hello, #{name}"
end
defmodule Example.Greetings do
def morning(name), do: "good morning, #{name}"
end
さらに、完全修飾名さえ使えば、親モジュールがなくても子モジュールは定められます。すべてのモジュール名はアトムに変換されるためです。
defmodule Example.Greetings.Japan do
def greeting(name), do: "こんにちは, #{name}"
end
iex> Example.Greetings.Japan.greeting("日本")
"こんにちは, 日本"
alias/import/require/useを複数使う
alias
やimport
、require
、use
は一度に複数のモジュールを定めることができます。つぎのコードは、ふたつの子モジュールにモジュール名のエイリアスを与える例です。
iex> alias Example.Greetings.{US, Japan}
[Example.Greetings.US, Example.Greetings.Japan]
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: つぎのステップ
番外
Posted on December 11, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.