Elixir入門 17: 内包表記
Tags: elixir, webdev, tutorial, programming
Published: 2019-01-15 01:15:00

---
title: Elixir入門 17: 内包表記
published: true
description: Elixir公式サイトの許諾を得て「Comprehensions」の解説にもとづき、加筆補正を加えて、Elixirで使える内包表記の構文についてご説明します。
tags: #elixir, #webdev, #tutorial, #programming
---

本稿はElixir公式サイトの許諾を得て「[Comprehensions](https://elixir-lang.org/getting-started/comprehensions.html)」の解説にもとづき、加筆補正を加えて、Elixirで使える内包表記の構文についてご説明します。

Elixirでは列挙可能なデータをループして取り出し、フィルタリングして、他のリストに値をマッピングするといったことがよくあります。内包表記はそうした処理の糖衣構文です。[`for/1`](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#for/1)を用いてつぎの3つの要素で組み立てます。

- ジェネレータ
- フィルタ
- コレクタブル

たとえば、つぎのコードは整数のリストの値を2乗してマッピングします。

```elixir
iex> for n <- [1, 2, 3, 4, 5], do: n * n
[1, 4, 9, 16, 25]
```

# ジェネレータ
もととなるデータ構造から値を取り出す式がジェネレータです。内包表記で`<-`の右辺からつくられた値が、順に左辺に渡されます。列挙型のデータであれば、ジェネレータ式の右辺に置けます。

```elixir
iex> for n <- 1..5, do: n * n
[1, 4, 9, 16, 25]
```

キーワードリストやマップ、バイナリなどもジェネレータで扱えます。

```elixir
iex> for {_key, val} <- [one: 1, two: 2, three: 3], do: val
[1, 2, 3]
iex> for {key, val} <- %{one: 1, two: 2, three: 3}, do: {key, val}
[one: 1, three: 3, two: 2]
iex> for <<char <- "hello">>, do: <<char>>
["h", "e", "l", "l", "o"]
```

ジェネレータ式の左辺にはパターンマッチングが使えます。パターンに一致しない値は無視されます。たとえば、キーワードリストのキーをパターンに用いたのがつぎの例です。

```elixir
iex> numbers = [identity: 1, prime: 2, prime: 3, normal: 4, prime: 5]
[identity: 1, prime: 2, prime: 3, normal: 4, prime: 5]
iex> for {:prime, n} <- numbers, do: n * n
[4, 9, 25]
```

ジェネレータは複数使えます。

```elixir
iex> for i <- [:a, :b, :c], j <- [1, 2], do:  {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
```

複数のジェネレータは、入れ子のループと捉えられます。

```elixir
iex> for i <- [1, 2, 3], j <- 1..i, do: [i: i, j: j]
[
  [i: 1, j: 1],
  [i: 2, j: 1],
  [i: 2, j: 2],
  [i: 3, j: 1],
  [i: 3, j: 2],
  [i: 3, j: 3]
]
```

なお、内包表記の中における変数の代入は、外には影響を与えません。


# フィルタ
ジェネレータから取り出す値は、フィルタで絞り込めます。フィルタとして定めるのは関数です。戻り値が`false`でも`nil`でもない値だけが使われます。

```elixir
iex> multiple_of_3? = fn(n) -> rem(n, 3) == 0 end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> for n <- 0..10, multiple_of_3?.(n), do: n * n
[0, 9, 36, 81]
```

フィルタは内包表記におけるガードと捉えればよいでしょう。組み込みの関数もフィルタとして用いることができます。

```elixir
iex> import Integer
Integer
iex> for n <- 0..10, is_even(n), do: n
[0, 2, 4, 6, 8, 10]
```

ジェネレータと同じく、フィルタも複数与えられます。

```elixir
iex> for n <- 0..100,
...>   is_odd(n),
...>   rem(n, 7) == 0,
...> do: n
[7, 21, 35, 49, 63, 77, 91]
```

内包表記を用いると、`Enum`や`Stream`モジュールの関数を使う処理がずっと簡潔に書けます。さらに、ジェネレータやフィルタをいくつも加えることができるのです。つぎのコードはディレクトリのリストからファイルのパスを探し、標準ファイルであることを確かめたうえで、それぞれのサイズをリストで出力します(図001)。

```elixir
# example.exs
dirs = ['home/mickey', 'home/minnie']
for dir  <- dirs,
    file <- File.ls!(dir),
    path = Path.join(dir, file),
    File.regular?(path) do
  IO.puts(File.stat!(path).size)
end
```

```terminal
$ elixir example.exs
6
7
```

#### 図001■ディレクトリ内のファイル
![elixir_17_001.png](https://thepracticaldev.s3.amazonaws.com/i/11jyulcxwddu56xrxvnl.png)

- [`File.ls!/1`](https://hexdocs.pm/elixir/File.html#ls!/1): ディレクトリの中のファイルのパスをリストで返します。
- [`File.regular?/1`](https://hexdocs.pm/elixir/File.html#regular?/1): パスが標準ファイルかどうかを論理値で返します。
- [`File.stat!/2`](https://hexdocs.pm/elixir/File.html#stat!/2): [ファイルの情報](https://hexdocs.pm/elixir/File.Stat.html#t:t/0)を構造体で返します。


# ビットストリングジェネレータ
ビットストリングもジェネレータで扱えます。ビットストリングのストリームを解析するのに便利です。たとえば、ピクセルのRGBカラー成分値をストリームで受け取って、ピクセルごとにRGB成分値のタプルにまとめることもできます。

```elixir
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
<<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: [r: r, g: g, b: b]
[
  [r: 213, g: 45, b: 132],
  [r: 64, g: 76, b: 32],
  [r: 76, g: 0, b: 0],
  [r: 234, g: 32, b: 15]
]
```


# :intoオプション
内包表記はデフォルトではリストを返します。けれども、結果はリスト以外のデータ構造に納めることもできるのです。その場合には`:into`オプションでそのデータ構造を与えます。つぎのコードは、文字リストから文字列を得る例です。

```elixir
iex> for c <- [104, 101, 108, 108, 111], into: "", do: <<c>>
"hello"
```

つぎの例は、文字列からスペースを除いて、文字列にして返します。

```elixir
iex> for <<char <- " to be, to be, ten made to be ">>, char != ?\s, into: "", do: <<char>>
"tobe,tobe,tenmadetobe"
```

`:into`オプションには、[`Collectable`プロトコル](https://hexdocs.pm/elixir/Collectable.html)を実装したデータ構造が与えられます。よく使われるのは、マップのキーはそのままにして値を変える場合です。

```elixir
iex> for {key, val} <- %{a: 1, b: 2, c: 3}, into: %{}, do: {key, val * val}
%{a: 1, b: 4, c: 9}
```

[`IO.stream/2`](https://hexdocs.pm/elixir/IO.html#stream/2)は、入力を[`IO.Stream`](https://hexdocs.pm/elixir/IO.Stream.html)にして返します。そして、`IO.Stream`の実装するプロトコルは、`Enumerable`と`Collectable`です。つぎのコードは、キーボードから入力した英字を[`String.upcase/2`](https://hexdocs.pm/elixir/String.html#upcase/2)で大文字にしてシェルに出力します。なお、入力待ちの状態から抜けるには、`IEx`を終了させてください。

```elixir
iex> stream = IO.stream(:stdio, :line)
%IO.Stream{device: :standard_io, line_or_bytes: :line, raw: false}
iex> for line <- stream, into: stream do
...>   String.upcase(line) <> "\n"
...> end
elixir  #<- 入力
ELIXIR  #<- 出力
```


#### 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: モジュールと関数](https://dev.to/gumi/elixir-08--1c4c)
- [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: 内包表記
- [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)