Elixir入門 08: モジュールと関数
Tags: elixir, webdev, tutorial, programming
Published: 2018-11-06 01:55:50

---
title: Elixir入門 08: モジュールと関数
published: true
description: Elixir公式サイトの許諾を得て「Modules and functions」の解説にもとづき、加筆補正を加えてElixirのモジュールと関数についてご説明します。
tags: #elixir, #webdev, #tutorial, #programming
---

本稿はElixir公式サイトの許諾を得て「[Modules and functions](https://elixir-lang.org/getting-started/modules-and-functions.html)」の解説にもとづき、加筆補正を加えてElixirのモジュールと関数についてご説明します。

Elixirはモジュールに関数をまとめてグループ化します。たとえば、[`String.length/1`](https://hexdocs.pm/elixir/String.html#length/1)はUTF-8のUnicode文字数を調べる`String`モジュールの関数です。

```elixir
iex> String.length("hello")
5
```

モジュールをつくるには、[`defmodule/2`マクロ](https://hexdocs.pm/elixir/Kernel.html#defmodule/2)を用います。さらに、その中に関数を定めるのが[`def/2`マクロ](https://hexdocs.pm/elixir/Kernel.html#def/2)です。モジュールと関数はつぎの構文でつくられます。

```elixir
iex> defmodule Math do
...>   def sum(a, b) do
...>     a + b
...>   end
...> end

iex> Math.sum(1, 2)
3
```

モジュールや関数を定めてコードが長くなると、`iex`モードではテストしにくくなるでしょう。コンパイルやスクリプトモードを使えば、ファイルに書いたプログラムが試せます。


# コンパイル
Elixirのプログラムを書いたファイルには拡張子`.ex`をつけます。たとえば、つぎのコードを`math.ex`というファイル名で保存したとしましょう。

```elixir
defmodule Math do
  def sum(a, b) do
    a + b
  end
end
```

コンパイルするには、コマンドラインツールから`elixirc ファイル名`を打ち込んでください。コンパイルされたバイトコードのファイルが`Elixir.モジュール名.beam`という名前でつくられるはずです(図001)。

```terminal
$ elixirc math.ex
```

#### 図001■モジュールのバイトコードがつくられた
![elixir_08_001.png](https://thepracticaldev.s3.amazonaws.com/i/781rg4ji2o5jnseq2irt.png)

そのあと`iex`を起ち上げれば、そのディレクトリにあるバイトコードファイル(`.beam`)が読み込まれ、モジュールも使えるようになるのです。

```elixir
iex> Math.sum(1, 2)
3
```

実際の開発には[`mix`](https://hexdocs.pm/mix/Mix.html)というビルドツールを使うことになるでしょう。プロジェクトの作成、コンパイル、テスト、依存関係の管理などがこのツールの役割です。Elixirのプロジェクトでは、通常つぎの3つのディレクトリにそれぞれのファイルを納めて開発します。

- ebin - コンパイルされたバイトコードファイル
- lib - Elixirコードファイル(拡張子`.ex`)
- test - テストファイル(拡張子`.exs`)


# スクリプトモード
スクリプトモードではバイトコードファイルはつくることなく、プログラムを実行します。ファイルにつける拡張子は`.exs`です。中身は`.ex`ファイルと同じで、コンパイルしたモジュールがメモリに読み込まれて実行されます。バイトコードをディスクに書き出さないことを示すため、異なる拡張子が使われるのです。

```elixir
defmodule Math do
  def sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)
```

スクリプトモードで実行するには、コマンドラインツールからコマンド`elixir ファイル名`入力します。プログラムから結果を出力するには。`IO.puts/2`関数をお使いください。

```terminal
$ elixir math.exs
3
```

モジュールを読み込んだうえで`iex`で使いたいときは、コマンドラインツールから`iex ファイル名`のコマンドで起ち上げてください。

```terminal
$ iex math.exs
```

```elixir
iex> Math.sum(3, 4)
7
```


# 名前つき関数
モジュールに`def/2`で定めた関数は、他のモジュールから呼び出せます。外から参照させないプライベートな関数を定義するために用いるのが[`defp/2`](https://hexdocs.pm/elixir/Kernel.html#defp/2)です。なお、ハッシュ記号`#`のあとの記述はコメントとして無視されます。

```elixir
defmodule Math do
  def sum(a, b) do
    do_sum(a, b)
  end

  defp do_sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)    #=> 3
IO.puts Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)
```

```terminal
$ elixir math.exs
3
** (UndefinedFunctionError) function Math.do_sum/2 is undefined or private
    Math.do_sum(1, 2)
    math.exs:12: (file)
    (elixir) lib/code.ex:677: Code.require_file/2
```

関数の定めには、ガードと複数の句が加えられます。複数の句は、Elixirが上から順に試し、マッチした句を実行するのです。引数がいずれにもマッチしなければ、エラーになります。なお、関数名の最後に疑問符`?`を添えるのは、論理値が返される場合のElixirにおける[命名規則](https://hexdocs.pm/elixir/master/naming-conventions.html#trailing-question-mark-foo)です。

```elixir
defmodule Math do
  def zero?(0) do
    true
  end

  def zero?(x) when is_integer(x) do
    false
  end
end

IO.puts Math.zero?(0)   #=> true
IO.puts Math.zero?(1)   #=> false
IO.puts Math.zero?([1]) #=> ** (FunctionClauseError)
IO.puts Math.zero?(0.0) #=> ** (FunctionClauseError)
```

`if/2`と同じく、名前つき関数には`do/end`ブロックのほか、`do:`を用いたキーワードリストの構文が使えます(「[Elixir入門 05: 条件 - case/cond/if](https://dev.to/gumi/elixir-05----casecondif-60o)」「do/endブロック」および「[Elixir入門 07: キーワードリストとマップ](https://dev.to/gumi/elixir-07--39hi)」「キーワードリスト」参照)。前掲コードは、つぎのようにも書けるのです。

```elixir
defmodule Math do
  def zero?(0), do: true
  def zero?(x) when is_integer(x), do: false
end
```

ただし、`do:`の構文は1行で済む場合に用い、複数行にわたるときは`do/end`ブロックで書いた方がよいでしょう。


# 関数のキャプチャ
前掲の`Math.zero?/1`が定められたモジュール(ファイル名math.exsとします)を例に、`iex`で関数のキャプチャを試してみましょう。

```terminal
$ iex math.exs
```

```elixir
iex> Math.zero?(0)
true
```

Elixirは無名関数と名前つき関数を区別します。無名関数の納められた変数から関数を呼び出すには、変数のあとにドット`.`を添えなければなりません(「[Elixir入門 02: 型の基本](https://dev.to/gumi/elixir-02--30n1)」「無名関数」参照)。

キャプチャ演算子[`&/1`](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#&/1)を用いると、名前つき関数を変数に入れて、無名関数と同じように呼び出せます。代入する関数には、アリティを添えてください。なお、[`is_function/1`](https://hexdocs.pm/elixir/Kernel.html#is_function/1)は引数が関数かどうかを確かめます。

```elixir
iex> fun = &Math.zero?/1
&Math.zero?/1
iex> is_function(fun)
true
iex> fun.(0)
true
```

組み込み済みの関数も、`&/1`演算子で変数に納めて呼び出せます。

```elixir
iex> is_fun = &(is_function/1)
&:erlang.is_function/1
iex> is_fun.(fun)
true
iex> (&is_number/1).(1.0)
true
```

`&/1`演算子を用いると無名関数も簡略に書けます。たとえば、つぎの無名関数を定めたいとしましょう。

```elixir
square = fn(x) -> x * x end
```

`&/1`演算子を使えば、つぎのように短く書けます。さらに、他の無名関数とも組み合わせられるのです。

```elixir
iex> square = &(&1 * &1)
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> square.(2)
4
iex> square_sum = &(square.(&1) + square.(&2))
#Function<12.99386804/2 in :erl_eval.expr/5>
iex> square_sum.(3, 4)
25
```

モジュールの関数もキャプチャすると、モジュールなしで呼び出せるようになります。[`List.flatten/2`](https://hexdocs.pm/elixir/List.html#flatten/2https://hexdocs.pm/elixir/List.html#flatten/2)は引数のふたつのリストをつなげたうえで、入れ子を平坦化する関数です。引数ふたつを与えていますので、キャプチャにアリティは添えません。

```elixir
iex> flatten = &List.flatten(&1, &2)
&List.flatten/2
iex> flatten.([1, [[2], 3]], [4, 5])
[1, 2, 3, 4, 5]
```

モジュールの関数を利用した新たな関数もつくれます。なお、[`math`](https://elixir-lang.org/getting-started/erlang-libraries.html#the-math-module)はErlangのモジュールで、[`:math.sqrt/1`](http://erlang.org/doc/man/math.html#sqrt-1)は平方根を求める関数です。

```elixir
iex> hypot = &:math.sqrt(square_sum.(&1, &2))
#Function<12.99386804/2 in :erl_eval.expr/5>
iex> hypot.(3, 4)
5.0
```

上の関数はつぎのようにも定められます。

```elixir
iex> hypot = &:math.sqrt(&1 * &1 + &2 * &2)
#Function<12.99386804/2 in :erl_eval.expr/5>
iex> hypot.(3, 4)
5.0
```


# デフォルト引数
名前つき関数の引数には、あとに`\\`に続けてデフォルト値が定められます。

```elixir
defmodule DefaultTest do
  def dowork(x \\ "hello") do
    x
  end
end
```

```elixir
iex> DefaultTest.dowork()
"hello"
iex> DefaultTest.dowork("hi")
"hi"
iex> DefaultTest.dowork(1)
1
```

デフォルト値にはどのような式でも与えられます。ただし、値が評価されるのは、関数を定義したときではありません。関数が呼び出されてデフォルト値が用いられるたびに、その値は評価されるのです。

```elixir
defmodule Concat do
  def join(a, b, sep \\ " ") do
    a <> sep <> b
  end
end
```

```elixir
iex(2)> Concat.join("hello", "world")
"hello world"
iex(3)> Concat.join("hello", "world", ", ")
"hello, world"
```

複数の句をもつ関数にも、デフォルト値は使えます。ただし、アリティを同じくする句には、関数本体のないヘッダで定めなければなりません。コンパイルエラーのメッセージにはその旨が示されます。

```elixir
defmodule Greeter do
  def hello(name \\ nil, language \\ "em") when is_nil(name) do
    phrase(language) <> "world"
  end

  def hello(name, language \\ "en") do
    phrase(language) <> name
  end

  defp phrase("en"), do: "hello, "
  defp phrase("ja"), do: "こんにちは"
end
```

```terminal
** (CompileError) greeter.ex:6: definitions with multiple clauses and default values requi
re a header. Instead of:
    def foo(:first_clause, b \\ :default) do ... end
    def foo(:second_clause, b) do ... end

one should write:

    def foo(a, b \\ :default)
    def foo(:first_clause, b) do ... end
    def foo(:second_clause, b) do ... end

def hello/2 has multiple clauses and defines defaults in one or more clauses
    greeter.ex:6: (module)
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
```

句が複数ある関数のデフォルト値は、ヘッダの引数に`\\`で与えてください。

```elixir
defmodule Greeter do
  def hello(name \\ nil, language \\ "en")

  def hello(name, language) when is_nil(name) do
    phrase(language) <> "world"
  end

  def hello(name, language) do
    phrase(language) <> name
  end

  defp phrase("en"), do: "hello, "
  defp phrase("ja"), do: "こんにちは"
end
```

```elixir
iex> Greeter.hello()
"hello, world"
iex> Greeter.hello("alice")
"hello, alice"
iex> Greeter.hello("太郎", "ja")
"こんにちは太郎"
```

デフォルト値を与えるときは、関数の定義が重複しないよう気をつけてください。

```elixir
defmodule Concat do
  def join(a, b) do
    IO.puts "#=> join/2"
    a <> b
  end

  def join(a, b, sep \\ " ") do
    IO.puts "#=> join/3"
    a <> sep <> b
  end
end
```

上の関数をコンパイルすると、つぎのような警告が示されます。ふたつの引数を渡すとつねにアリティ2の関数が呼び出され、アリティ3のデフォルト値が使われることはないからです。

```terminal
warning: this clause cannot match because a previous clause at line 2 always matches
```

エラーではありませんので、コンパイルはでき、関数も呼び出せます。アリティ3の関数は3つの引数を渡さないかぎり呼び出されません。引数がふたつのときどういう結果を得たいのか考え直すべきでしょう。

```elixir
iex(1)> Concat.join("hello", "world")
#=> join/2
"helloworld"
iex(2)> Concat.join("hello", "world", ", ")
#=> join/3
"hello, world"
```

つぎのコードは、関数のデフォルト値とパターンマッチングを使った例です。[`Enum.join/2`](https://hexdocs.pm/elixir/Enum.html#join/2)は、リスト([`Enumerable`](https://hexdocs.pm/elixir/Enumerable.html))要素の間に第2引数の文字列を挟んで、バイナリ(文字列)につなげる関数です。

```elixir
defmodule Greeter do
  def hello(names, language \\ "en")

  def hello(names, language) when is_list(names) do
    hello(Enum.join(names, ", "), language)
  end

  def hello(name, language) when is_binary(name) do
    phrase(language) <> name
  end

  defp phrase("en"), do: "hello, "
  defp phrase("ja"), do: "こんにちは"
end
```

```elixir
iex> Greeter.hello("alice")
"hello, alice"
iex> Greeter.hello(["alice", "carroll"])
"hello, alice, carroll"
iex> Greeter.hello(["桃太郎", "金太郎", "浦島太郎"], "ja")
"こんにちは桃太郎, 金太郎, 浦島太郎"
```

なお、上のコードの最初の関数は、パイプ演算子[`|>`](https://hexdocs.pm/elixir/Kernel.html#%7C%3E/2)を用いると、つぎのようにすっきりと書き替えられます。`|>`は左オペランドの値を、右オペランドの関数に第1引数として渡す演算子です。

```elixir
def hello(names, language) when is_list(names) do
  # hello(Enum.join(names, ", "), language)
  names
  |> Enum.join(", ")
  |> hello(language)
end
```

#### Elixir入門もくじ
- [Elixir入門 01: コードを書いて試してみる](https://dev.to/gumi/elixir-01--2585)
- [Elixir入門 02: 型の基本](https://dev.to/gumi/elixir-02--30n1)
- [Elixir入門 03: 演算子の基本](https://dev.to/gumi/elixir-03--33im)
- [Elixir入門 04: パターンマッチング](https://dev.to/gumi/elixir-04--1346)
- [Elixir入門 05: 条件 - case/cond/if](https://dev.to/gumi/elixir-05----casecondif-60o)
- [Elixir入門 06: バイナリと文字列および文字リスト](https://dev.to/gumi/elixir-06--35na)
- [Elixir入門 07: キーワードリストとマップ](https://dev.to/gumi/elixir-07--39hi)
- Elixir入門 08: モジュールと関数
- [Elixir入門 09: 再帰](https://dev.to/gumi/elixir-09--1a0p)
- [Elixir入門 10: EnumとStream](https://dev.to/gumi/elixir-10-enumstream-4fpb)
- [Elixir入門 11: プロセス](https://dev.to/gumi/elixir-11--2mia)
- [Elixir入門 12: 入出力とファイルシステム](https://dev.to/gumi/elixir-12--4og6)
- [Elixir入門 13: aliasとrequireおよびimport](https://dev.to/gumi/elixir-13-aliasrequireimport-55c1)
- [Elixir入門 14: モジュールの属性](https://dev.to/gumi/elixir-14--3511)
- [Elixir入門 15: 構造体](https://dev.to/gumi/elixir-15--4f43)
- [Elixir入門 16: プロトコル](https://dev.to/gumi/elixir-16--lif)
- [Elixir入門 17: 内包表記](https://dev.to/gumi/elixir-17--5gci)
- [Elixir入門 18: シギル](https://dev.to/gumi/elixir-18--5791)
- [Elixir入門 19: tryとcatchおよびrescue](https://dev.to/gumi/elixir-19-trycatchrescue-50i8)
- [Elixir入門 20: 型の仕様とビヘイビア](https://dev.to/gumi/elixir-20--j50)
- [Elixir入門 21: デバッグ](https://dev.to/gumi/elixir-21--21a1)
- [Elixir入門 22: Erlangライブラリ](https://dev.to/gumi/elixir-22-erlang-2492)
- [Elixir入門 23: つぎのステップ](https://dev.to/gumi/elixir-23--50ik)

##### 番外
- [Elixir入門: Plugを使うには](https://dev.to/gumi/elixir-plug-40lb)