本稿はElixir公式サイトの許諾を得て「Comprehensions」の解説にもとづき、加筆補正を加えて、Elixirで使える内包表記の構文についてご説明します。
Elixirでは列挙可能なデータをループして取り出し、フィルタリングして、他のリストに値をマッピングするといったことがよくあります。内包表記はそうした処理の糖衣構文です。for/1
を用いてつぎの3つの要素で組み立てます。
- ジェネレータ
- フィルタ
- コレクタブル
たとえば、つぎのコードは整数のリストの値を2乗してマッピングします。
iex> for n <- [1, 2, 3, 4, 5], do: n * n
[1, 4, 9, 16, 25]
ジェネレータ
もととなるデータ構造から値を取り出す式がジェネレータです。内包表記で<-
の右辺からつくられた値が、順に左辺に渡されます。列挙型のデータであれば、ジェネレータ式の右辺に置けます。
iex> for n <- 1..5, do: n * n
[1, 4, 9, 16, 25]
キーワードリストやマップ、バイナリなどもジェネレータで扱えます。
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"]
ジェネレータ式の左辺にはパターンマッチングが使えます。パターンに一致しない値は無視されます。たとえば、キーワードリストのキーをパターンに用いたのがつぎの例です。
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]
ジェネレータは複数使えます。
iex> for i <- [:a, :b, :c], j <- [1, 2], do: {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
複数のジェネレータは、入れ子のループと捉えられます。
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
でもない値だけが使われます。
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]
フィルタは内包表記におけるガードと捉えればよいでしょう。組み込みの関数もフィルタとして用いることができます。
iex> import Integer
Integer
iex> for n <- 0..10, is_even(n), do: n
[0, 2, 4, 6, 8, 10]
ジェネレータと同じく、フィルタも複数与えられます。
iex> for n <- 0..100,
...> is_odd(n),
...> rem(n, 7) == 0,
...> do: n
[7, 21, 35, 49, 63, 77, 91]
内包表記を用いると、Enum
やStream
モジュールの関数を使う処理がずっと簡潔に書けます。さらに、ジェネレータやフィルタをいくつも加えることができるのです。つぎのコードはディレクトリのリストからファイルのパスを探し、標準ファイルであることを確かめたうえで、それぞれのサイズをリストで出力します(図001)。
# 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
$ elixir example.exs
6
7
図001■ディレクトリ内のファイル
-
File.ls!/1
: ディレクトリの中のファイルのパスをリストで返します。 -
File.regular?/1
: パスが標準ファイルかどうかを論理値で返します。 -
File.stat!/2
: ファイルの情報を構造体で返します。
ビットストリングジェネレータ
ビットストリングもジェネレータで扱えます。ビットストリングのストリームを解析するのに便利です。たとえば、ピクセルのRGBカラー成分値をストリームで受け取って、ピクセルごとにRGB成分値のタプルにまとめることもできます。
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
オプションでそのデータ構造を与えます。つぎのコードは、文字リストから文字列を得る例です。
iex> for c <- [104, 101, 108, 108, 111], into: "", do: <<c>>
"hello"
つぎの例は、文字列からスペースを除いて、文字列にして返します。
iex> for <<char <- " to be, to be, ten made to be ">>, char != ?\s, into: "", do: <<char>>
"tobe,tobe,tenmadetobe"
:into
オプションには、Collectable
プロトコルを実装したデータ構造が与えられます。よく使われるのは、マップのキーはそのままにして値を変える場合です。
iex> for {key, val} <- %{a: 1, b: 2, c: 3}, into: %{}, do: {key, val * val}
%{a: 1, b: 4, c: 9}
IO.stream/2
は、入力をIO.Stream
にして返します。そして、IO.Stream
の実装するプロトコルは、Enumerable
とCollectable
です。つぎのコードは、キーボードから入力した英字をString.upcase/2
で大文字にしてシェルに出力します。なお、入力待ちの状態から抜けるには、IEx
を終了させてください。
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: コードを書いて試してみる
- Elixir入門 02: 型の基本
- Elixir入門 03: 演算子の基本
- Elixir入門 04: パターンマッチング
- Elixir入門 05: 条件 - case/cond/if
- Elixir入門 06: バイナリと文字列および文字リスト
- Elixir入門 07: キーワードリストとマップ
- Elixir入門 08: モジュールと関数
- Elixir入門 09: 再帰
- Elixir入門 10: EnumとStream
- Elixir入門 11: プロセス
- Elixir入門 12: 入出力とファイルシステム
- Elixir入門 13: aliasとrequireおよびimport
- Elixir入門 14: モジュールの属性
- Elixir入門 15: 構造体
- Elixir入門 16: プロトコル
- Elixir入門 17: 内包表記
- Elixir入門 18: シギル
- Elixir入門 19: tryとcatchおよびrescue
- Elixir入門 20: 型の仕様とビヘイビア
- Elixir入門 21: デバッグ
- Elixir入門 22: Erlangライブラリ
- Elixir入門 23: つぎのステップ
Top comments (0)