Elixir入門 13: aliasとrequireおよびimport

gumitech

gumi TECH

Posted on December 11, 2018

Elixir入門 13: aliasとrequireおよびimport

本稿はElixir公式サイトの許諾を得て「alias, require, and import」の解説にもとづき、加筆補正を加えて、Elixirのディレクティブaliasrequireおよび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
Enter fullscreen mode Exit fullscreen mode
iex> Example.greeting("world")
"hello, world"
Enter fullscreen mode Exit fullscreen mode

第2引数を省くと、ドット(.)で区切られたモジュール名の最後の識別子がエイリアスになります。

defmodule Example do
  alias Sayings.Greetings
  def greeting(name), do: Greetings.basic(name)
end
Enter fullscreen mode Exit fullscreen mode

第2引数で与える名前には、as:オプションを添えてください。エイリアス名は大文字ではじめなければなりません。

defmodule Example do
  alias Sayings.Greetings, as: Hi
  def greeting(name), do: Hi.basic(name)
end
Enter fullscreen mode Exit fullscreen mode

Elixirのモジュールはすべて名前空間Elixirに定められます。デフォルトではそれが省けるということです。

iex> alias Example, as: String
Example
iex> String.greeting("tokyo")
"hello, tokyo"
iex> Elixir.String.length("hello")
5
Enter fullscreen mode Exit fullscreen mode

エイリアスはレキシカルスコープをもちます。モジュールで定めれば、モジュール内の関数すべてがそのエイリアスを参照できます。

defmodule Example do
  alias Sayings.Greetings, as: Hi
  def greeting(name), do: Hi.basic(name)
  def greeting_ex(name), do: Hi.basic(name) <> "!!"
end
Enter fullscreen mode Exit fullscreen mode
iex> Example.greeting_ex("world")
"hello, world!!"
Enter fullscreen mode Exit fullscreen mode

エイリアスを関数の中で定めると、他の関数からは参照できません。

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

require

Elixirには、メタプログラミングの仕組みとしてマクロが備わっています。メタプログラミングとは、コードが生成されるコードを書くことです。マクロはコンパイルのとき展開されます。

パブリックの関数はグローバルに用いることができます。けれど、マクロを使うには、それが定義されたモジュールをrequire/2ディレクティブでオプトインしなければなりません。

Integer.is_odd/1はモジュールにマクロとして定められています(図001)。そのため、あらかじめIntegerモジュールをrequire/2で設定しなければ使えません。

図001■リファレンスのInteger.is_odd/1の項にmacroの表示

elixir_13_001.png

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

ディレクティブ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]
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

第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]
Enter fullscreen mode Exit fullscreen mode
iex> import Integer, only: :functions
Integer
iex> digits(123)
[1, 2, 3]
iex> is_odd(3)
** (CompileError) iex: undefined function is_odd/1
Enter fullscreen mode Exit fullscreen mode

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

import/2ディレクティブもレキシカルスコープです。関数の中に定めると、他の関数からは参照できず、コンパイルエラーになります。

defmodule Example do
  def split(number) do
    import Integer, only: [digits: 1]
    digits(number)
  end
  def test(name), do: digits(name)  #コンパイルエラー
end
Enter fullscreen mode Exit fullscreen mode

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

別のモジュールでuse/2を用いると、__using__/1コールバックに定めた関数が、そのモジュールから呼び出せます。

defmodule Example do
  use Hello
end
Enter fullscreen mode Exit fullscreen mode
iex> Example.greeting("world")
"hello, world"
Enter fullscreen mode Exit fullscreen mode

use/2の機能は、つぎのようにrequire/2を使ったのと同じです。

defmodule Example do
  # use Hello
  require Hello
  Hello.__using__(greeting: :value)
end
Enter fullscreen mode Exit fullscreen mode

さらに、__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
Enter fullscreen mode Exit fullscreen mode
iex> Example.greeting("日本")
"こんにちは,日本"
Enter fullscreen mode Exit fullscreen mode

エイリアスを理解する

Elixirのエイリアスは、コンパイルするときアトムに変換される頭が大文字の識別子(StringKeywordなど)です。

iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> String == :"Elixir.String"
true
Enter fullscreen mode Exit fullscreen mode

alias/2を用いると、エイリアスはアトムに展開されます。Erlang VM(およびElixir)では、モジュールはアトムで表されるからです。

iex> :lists.flatten([1, [[2], 3]])
[1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

モジュールを入れ子にする

モジュールは入れ子にすることができます。外から子のモジュールを参照するには、親からの完全修飾名を用いなければなりません。

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

親モジュールの中に子のモジュールが定められたあとであれば、子は完全修飾名を使わずに参照できます。子モジュールが親のレキシカルスコープに含まれるため、内部的にエイリアスがつくられるからです。

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
Enter fullscreen mode Exit fullscreen mode
iex> Example.call_child("japan")
"good morning, japan"
Enter fullscreen mode Exit fullscreen mode

完全修飾名を用いれば、子モジュールは親の外でも定められます。このとき、子モジュールは必ずしも親のあとに書かなくても構いません。このとき親モジュールが子の名前だけで参照したいときには、エイリアスを使ってください。

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

defmodule Example.Greetings do
  def morning(name), do: "good morning, #{name}"
end
Enter fullscreen mode Exit fullscreen mode

さらに、完全修飾名さえ使えば、親モジュールがなくても子モジュールは定められます。すべてのモジュール名はアトムに変換されるためです。

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

alias/import/require/useを複数使う

aliasimportrequireuseは一度に複数のモジュールを定めることができます。つぎのコードは、ふたつの子モジュールにモジュール名のエイリアスを与える例です。

iex> alias Example.Greetings.{US, Japan}
[Example.Greetings.US, Example.Greetings.Japan]
Enter fullscreen mode Exit fullscreen mode

Elixir入門もくじ

番外
💖 💪 🙅 🚩
gumitech
gumi TECH

Posted on December 11, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related