loading...
gumi TECH Blog

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

gumitech profile image gumi TECH Updated on ・4 min read

本稿は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
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の表示

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

ディレクティブ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のエイリアスは、コンパイルするときアトムに変換される頭が大文字の識別子(StringKeywordなど)です。

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を複数使う

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

iex> alias Example.Greetings.{US, Japan}
[Example.Greetings.US, Example.Greetings.Japan]

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
 

Good post. Why not use English here?

 
 

It's translation article for english article.
You can see the original english article.
elixir-lang.org/getting-started/al...