プログラミング Elixir 第七章

# 概要

[プログラミング Elixir](https://www.amazon.co.jp/dp/4274219151)

リストを使った再帰関数の作り方を解説している章。

# ヘッドとテイル

リストは空でなければ、head と tail によって構成されている。head は値、tail はリストになっている。また、head と tail はパイプ文字 `|` で分けて表すことができる。

[2] を head と tail で分けて表すと `[2 | []]` となる。

```iex
iex> [2 | []] == [2]
true
```

[1, 2] を分けて表すと `[1 | [2]]` と分けることができ、さらに上記から

```iex
iex> [1 | [2 | []]] == [1, 2]
true
```

となることがわかる。

パターンの中でもパイプ文字は使うことができ、以下の様に head と tail を分けて取得できる。

```iex
iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]
```

# head と tail を再帰に利用する

パイプ文字で head と tail を取得できる機能を使って再帰関数を作成することができる。

```ex
defmodule Sample do
  def list_sum([]), do: 0
  def list_sum([head | tail]), do: head + list_sum(tail)
end
```

```iex
iex> Sample.list_sum([1, 2, 3])
6
iex> Sample.list_sum([])
0
```

引数のリストが空でない間は 2 つめのパターンにマッチし続けて、最後に 0 を返す。

## map 関数の作成

リストと関数を引数に取り、渡されたリストの各要素に関数を適用させる map 関数を作成する。

```ex
defmodule Sample do
  def map([], _func), do: []
  def map([head | tail], func), do: [func.(head) | map(tail, func)]
end
```

渡した `func` 関数を head に適用していって、リストが空になったら空のリスト返す。
最初の定義では `func` が使われないので `_` を付けて使わないことを明示する。

```iex
iex> Sample.map [1, 2, 3, 4], fn (n) -> n * n end
[1, 4, 9, 16]
iex> Sample.map [1, 2, 3, 4], fn (n) -> n + 1 end
[2, 3, 4, 5]
iex> Sample.map [1, 2, 3, 4], fn (n) -> n + n end
[2, 4, 6, 8]
```

& ショートカットを使うこともできる。

```iex
iex> Sample.map [1, 2, 3, 4], &(&1 + &1)
[2, 4, 6, 8]
```

## 再帰中の値の保持

足し算の合計を再帰で計算する際など、途中の合計を保持したい場合に、引数に状態を渡す方法で実現する。

```ex
defmodule Sample do
  def sum([], total), do: total
  def sum([head | tail], total), do: sum(tail, head + total)
end
```

```iex
iex> Sample.sum([1, 2, 3, 4, 5], 0)
15
iex> Sample.sum([11, 12, 13, 14, 15], 0)
65
```

しかし、この方法で実装した場合、最初に必ず無駄な引数である 0 を渡さなければならず、あまりうれしくない。そこで、リストを受け取る関数を public にし、実際の処理を private で定義する。

```ex
defmodule Sample2 do
  def sum(list), do: _sum(list, 0)

  defp _sum([], total), do: total
  defp _sum([head | tail], total), do: _sum(tail, head + total)
end
```

```iex
iex> Sample2.sum([1, 2, 3, 4, 5])
15
iex> Sample2.sum([11, 12, 13, 14, 15])
65
```

実際の処理を private にして外から呼び出せない様にし、public な関数に `_` を付けた名前にすることで、`sum` と `_sum` に関連があることを人が見てもわかる様にしている。

### `sum` 関数の一般化

`map` の例と同じように `sum` を一般化した `reduce` 関数を実装する。

`sum` の `head + total` の部分を関数を適用するようにし、さらに `map` 関数でみたように関数を渡せるようにしてあげれば良い。

```ex
defmodule Sample do
  def reduce([], value, _), do: value
  def reduce([head | tail], value, func), do: reduce(tail, func.(head, value), func)
end
```

```iex
iex> Sample.reduce([1, 2, 3, 4, 5], 0, &(&1 + &2))
15
iex> Sample.reduce([1, 2, 3, 4, 5], 1, &(&1 * &2))
120
```

# 複雑なリストのパターン

リストは、複数の要素をパイプ文字の左におくこともできる。

```iex
iex> [1, 2 | [3, 4]]
[1, 2, 3, 4]
```

そのため、以下の様に head を個別にマッチさせることもできる。

```iex
iex> [a, b | c ] = [1, 2 | [3, 4]]
[1, 2, 3, 4]
iex> a
1
iex> b
2
iex> c
[3, 4]
```

これを利用して、リスト内の値のペアをスワップする関数を実装する。

```ex
defmodule Sample do
  def swap([]), do: []
  def swap([a, b | tail]), do: [b, a | swap(tail)]
  def swap([_]), do: raise "Can't swap a list with an odd number of elements"
end
```

```iex
iex> Sample.swap([1, 2, 3, 4])
[2, 1, 4, 3]
iex> Sample.swap([])
[]
iex> Sample.swap([1, 2, 3])
** (RuntimeError) Can't swap a list with an odd number of elements
    iex:45: Sample.swap/1
    iex:44: Sample.swap/1
```

## リストのリスト

`[id, class, name, sex]` の属性を持った生徒のデータがあった際に、class が 6 の値を持った生徒のデータを取り出したいとする。

```ex
students = [
  [1, 1, 'taro', 'man'],
  [2, 1, 'hanako', 'woman'],
  [3, 2, 'jiro', 'man'],
  [4, 6, 'saburo', 'man'],
  [5, 3, 'hanae', 'woman'],
  [6, 2, 'shiro', 'man'],
  [7, 6, 'hanami', 'woman'],
  [8, 5, 'hanaka', 'woman'],
  [9, 6, 'goro', 'man']
]

defmodule Student do
  def fetch_class_6([]), do: []
  def fetch_class_6([[id, 6, name, sex] | tail]), do: [[id, 6, name, sex] | fetch_class_6(tail)]
  def fetch_class_6([_ | tail]), do: fetch_class_6(tail)
end
```

```iex
iex> Student.fetch_class_6(students)
[[4, 6, 'saburo', 'man'], [7, 6, 'hanami', 'woman'], [9, 6, 'goro', 'man']]
```

このままだと class の値が 6 のデータしか取得できないので、好きな class を指定できる様にする。

```ex
defmodule Student do
  def fetch_by_class([], class_num), do: []
  def fetch_by_class([[id, class_num, name, sex] | tail], class_num), do: [[id, class_num, name, sex] | fetch_by_class(tail, class_num)]
  def fetch_by_class([_ | tail], class_num), do: fetch_by_class(tail, class_num)
end
```

```iex
iex> Student.fetch_by_class(students, 6)
[[4, 6, 'saburo', 'man'], [7, 6, 'hanami', 'woman'], [9, 6, 'goro', 'man']]
iex> Student.fetch_by_class(students, 5)
[[8, 5, 'hanaka', 'woman']]
iex> Student.fetch_by_class(students, 1)
[[1, 1, 'taro', 'man'], [2, 1, 'hanako', 'woman']]
```

`[id, class_num, name, sex]` が複数回出てくるのと、id, name, sex は要素数をチェックしているだけで、特に関数内で使用されないため、少しリファクタリングすると次のようになる

```ex
defmodule Student do
  def fetch_by_class([], class_num), do: []
  def fetch_by_class([head = [_, class_num, _, _] | tail], class_num), do: [head | fetch_by_class(tail, class_num)]
  def fetch_by_class([_ | tail], class_num), do: fetch_by_class(tail, class_num)
end
```