8 モジュール - Modules

Elixirではモジュールのなかに複数の関数を入れてグループ化します.以前の章で既にいくつものモジュールを使ってきました.例えばStringモジュールをこんな感じに:

In Elixir we group several functions into modules. We've already used many different modules in the previous chapters like the String module:

iex> String.length "hello"
5

自分達でモジュールを作るにはdefmoduleマクロを使います.そのマクロの中でdefマクロを使って関数を定義します:

In order to create our own modules in Elixir, we use the defmodule macro. We use the def macro to define functions in that module:

iex> defmodule Math do
...>   def sum(a, b) do
...>     a + b
...>   end
...> end

iex> Math.sum(1, 2)
3

この後の節では例がもうちょっと複雑になり,シェルから入力するにはすごく注意しなければなりません.この機会に,どうやってElixirのコードをコンパイルするか,どうやってElixirのスクリプトを実行するかを学びましょう.

In the following sections, our examples are going to get a bit more complex, and it can be tricky to type them all in the shell. It's about time for us to learn how to compile Elixir code and also how to run Elixir scripts.

8.1 コンパイル - Compilation

モジュールをファイルへ書くことの便利さの多くは,それがコンパイルできて再利用できるところにあります.今,ファイルの名前がmath.exで,以下の内容が書いてあるとしましょう:

Most of the time it is convenient to write modules into files so they can be compiled and reused. Let's assume we have a file named math.ex with the following contents:

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

このファイルはelixircを使ってコンパイルできます:

This file can be compiled using elixirc:

$ elixirc math.ex

こうすると,定義されたモジュールのバイトコードを含むElixir.Math.beamという名前のファイルが生成されるでしょう.もう一度iexを開始すると,定義したモジュールが使えるようになっています(iexが開始したときに同じディレクトリにあるバイトコードファイルは自動的に読み込まれます):

This will generate a file named Elixir.Math.beam containing the bytecode for the defined module. If we start iex again, our module definition will be available (provided that iex is started in the same directory the bytecode file is in):

iex> Math.sum(1, 2)
3

Elixirは通常3つのディレクトリを備えています:

Elixir projects are usually organized into three directories:

  • ebin - コンパイルされたバイトコードが入っています
  • lib - Elixirのコードが入っています(たいてい.exというファイルです)
  • test - テストが入っています(たいて.exsというファイルです)

  • ebin - contains the compiled bytecode

  • lib - contains elixir code (usually .ex files)

  • test - contains tests (usually .exs files)

実際のプロジェクトでは,mixというビルドツールがコンパイルと適切なパスのセッティングをうまくやってくれます.学習目的のため,Elixirではコンパイル成果物を作らずに融通がきくスクリプトモードというものを使えます.

When working on actual projects, the build tool called mix will be responsible for compiling and setting up the proper paths for you. For learning purposes, Elixir also supports a scripted mode which is more flexible and does not generate any compiled artifacts.

8.2 スクリプトモード - Scripted mode

.exというElixirの拡張子の他に,スクリプティング用に.exsというファイルも使えます.Elixirは2つのファイルを同じように扱います,違いは意図だけです..exファイルはコンパイルされることを意図しています,一方.exsはコンパイル不要でスクリプティングに使われます.例えばmath.exsと呼ばれているファイルを作ることができ:

In addition to the Elixir file extension .ex, Elixir also supports .exs files for scripting. Elixir treats both files exactly the same way, the only difference is in intention. .ex files are meant to be compiled while .exs files are used for scripting, without the need for compilation. For instance, we can create a file called math.exs:

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)

そしてこのように実行できます:

And execute it as:

$ elixir math.exs

ファイルはメモリ内でコンパイルされ,実行され,結果として3を表示します.バイトコードファイルは作られません.今後の例では,上で見たようにスクリプトファイルへコードを書いて実行することをおすすめします.

The file will be compiled in memory and executed, printing "3" as the result. No bytecode file will be created. In the following examples, we recommend you write your code into script files and execute them as shown above.

8.3 名前付き関数 - Named functions

モジュールのなかで,def/2を使って関数を定義したりdefp/2を使ってプライベート関数を定義できます.def/2で定義した関数は別のモジュールから呼び出せるのに対し,プライベート関数はモジュールの中だけでしか呼び出せません.

Inside a module, we can define functions with def/2 and private functions with defp/2. A function defined with def/2 can be invoked from other modules while a private function can only be invoked locally.

defmodule Math do
  def sum(a, b) do
    do_sum(a, b)
  end

  defp do_sum(a, b) do
    a + b
  end
end

Math.sum(1, 2)    #=> 3
Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)

関数宣言はガードと複数句に対応しています.もし関数に複数の句がある場合,Elixirはマッチする句があるまで順番に試していきます.引数が0かそうではないかをチェックする関数はこうなります:

Function declarations also support guards and multiple clauses. If a function has several clauses, Elixir will try each clause until it finds one that matches. Here is an implementation of a function that checks if the given number is zero or not:

defmodule Math do
  def zero?(0) do
    true
  end

  def zero?(x) when is_number(x) do
    false
  end
end

Math.zero?(0)  #=> true
Math.zero?(1)  #=> false

Math.zero?([1,2,3])
#=> ** (FunctionClauseError)

引数がどの句にもマッチしなかった場合はエラーが発生します.

Giving an argument that does not match any of the clauses raises an error.

8.4 関数のキャプチャ - Function capturing

このチュートリアルのあいだ,関数を指すのにname/arityという記法を使ってきました.実はこの記法を名前付き関数の型として使うこともできます.iexを開始して上で定義したmath.exsを動かしてみましょう:

Throughout this tutorial, we have been using the notation name/arity to refer to functions. It happens that this notation can actually be used to retrieve a named function as a function type. Let's start iex and run the math.exs file defined above:

$ iex math.exs
iex> Math.zero?(0)
true
iex> fun = &Math.zero?/1
&Math.zero?/1
iex> is_function fun
true
iex> fun.(0)
true

is_function/1のような,ローカルな関数やインポートされた関数はモジュールをつけなくてもキャプチャできます:

Local or imported functions, like is_function/1, can be captured without the module:

iex> &is_function/1
&:erlang.is_function/1
iex> (&is_function/1).(fun)
true

キャプチャ構文は関数を作るときのショートカットとしても使えます:

Note the capture syntax can also be used as a shortcut for creating functions:

iex> fun = &(&1 + 1)
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2

&1は関数に渡された最初の引数を表します.上の&(&1+1)fn x -> x + 1 endと同じです.この構文は短い関数定義をするのに便利です.キャプチャ演算子&についてはKernel.SpecialFormsのドキュメントで詳しく読むことができます.

The &1 represents the first argument passed into the function. &(&1+1) above is exactly the same as fn x -> x + 1 end. The syntax above is useful for short function definitions. You can read more about the capture operator & in the Kernel.SpecialForms documentation.

8.5 デフォルト引数 - Default arguments

名前つき関数ではデフォルト引数も使えます:

Named functions in Elixir also support default arguments:

defmodule Concat do
  def join(a, b, sep \\ " ") do
    a <> sep <> b
  end
end

IO.puts Concat.join("Hello", "world")      #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world

デフォルト値としてどんな式も用意しておけますが,関数定義のときには評価されません; 後で使うため単に保存しします.関数が呼び出され,その関数のデフォルト値が利用されるたび,デフォルト値の式が評価されます.

Any expression is allowed to serve as a default value, but it won't be evaluated during the function definition; it will simply be stored for later use. Every time the function is invoked and any of its default values have to be used, the expression for that default value will be evaluated:

defmodule DefaultTest do
  def dowork(x \\ IO.puts "hello") do
    x
  end
end
iex> DefaultTest.dowork 123
123
iex> DefaultTest.dowork
hello
:ok

もしデフォルト値つきの関数に複数の句がある場合,デフォルト値を宣言する(動作内容を含めない)ヘッダ部分を分けて作っておくことをおすすめします:

If a function with default values has multiple clauses, it is recommended to create a function head (without an actual body), just for declaring defaults:

defmodule Concat do
  def join(a, b \\ nil, sep \\ " ")

  def join(a, b, _sep) when is_nil(b) do
    a
  end

  def join(a, b, sep) do
    a <> sep <> b
  end
end

IO.puts Concat.join("Hello", "world")      #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
IO.puts Concat.join("Hello")               #=> Hello

もしデフォルト値を使うなら,関数定義の上書きについてだけは十分に注意しなければなりません.以下の例を考えてみましょう:

When using default values, one must be careful to avoid overlapping function definitions. Consider the following example:

defmodule Concat do
  def join(a, b) do
    IO.puts "***First join"
    a <> b
  end

  def join(a, b, sep \\ " ") do
    IO.puts "***Second join"
    a <> sep <> b
  end
end

上のコードを"concat.ex"というファイル名でセーブしコンパイルすると,Elixirは以下の警告を出力するでしょう:

If we save the code above in a file named "concat.ex" and compile it, Elixir will emit the following warning:

concat.ex:7: this clause cannot match because a previous clause at line 2 always matches

2引数のjoin関数は常に最初のjoin定義を呼び出すことになり,一方2番目のものは3つの引数が渡されたときにしか呼び出されませんということをコンパイラが私たちへ教えてくれています:

The compiler is telling us that invoking the join function with two arguments will always choose the first definition of join whereas the second one will only be invoked when three arguments are passed:

$ iex concat.exs
iex> Concat.join "Hello", "world"
***First join
"Helloworld"
iex> Concat.join "Hello", "world", "_"
***Second join
"Hello_world"

モジュールの簡単な導入はおしまいです.次の章では名前つき関数を再帰に使うにはどうすればよいか学び,他のモジュールから関数を導入するのに使われるレキシカルディレクティブを調査し,モジュールの属性について話すことにします.

This finishes our short introduction to modules. In the next chapters, we will learn how to use named functions for recursion, explore Elixir lexical directives that can be used for importing functions from other modules and discuss module attributes.