Do you want to pick up from where you left of?
Take me there

内包表記

リスト内包表記はElixirで列挙体 (enumerable) をループするための糖衣構文です。このレッスンでは反復や生成に内包表記を用いる方法を見ていきます。

基本

内包表記は EnumStream を反復するためのより手軽な文を生成するためによく用いられます。まずは単純な内包表記を見ていき、それから解説していきましょう:

iex> list = [1, 2, 3, 4, 5]
iex> for x <- list, do: x*x
[1, 4, 9, 16, 25]

最初に気づくのは for とジェネレータの使い方です。ジェネレータとは何でしょうか。リスト内包表記の x <- [1, 2, 3, 4] といった式のことです。次の値を生成する責任を負っています。

幸い、内包表記はリストだけに限定されず、実際列挙可能なものならどんなものにでも使えます:

# キーワードリスト
iex> for {_key, val} <- [one: 1, two: 2, three: 3], do: val
[1, 2, 3]

# マップ
iex> for {k, v} <- %{"a" => "A", "b" => "B"}, do: {k, v}
[{"a", "A"}, {"b", "B"}]

# バイナリ
iex> for <<c <- "hello">>, do: <<c>>
["h", "e", "l", "l", "o"]

お気づきかもしれませんが、ジェネレータは入力値セットと左辺の変数を比較するのにパターンマッチを利用しています。マッチするものが見つからない場合には、値は無視されます:

iex> for {:ok, val} <- [ok: "Hello", error: "Unknown", ok: "World"], do: val
["Hello", "World"]

入れ子になったループのように、複数のジェネレータを用いることも可能です:

iex> list = [1, 2, 3, 4]
iex> for n <- list, times <- 1..n do
...>   String.duplicate("*", times)
...> end
["*", "*", "**", "*", "**", "***", "*", "**", "***", "****"]

ループが発生しているさまをより明確に表現するため、 IO.puts を用いて2つのジェネレータが生成した値を表示してみましょう:

iex> for n <- list, times <- 1..n, do: IO.puts "#{n} - #{times}"
1 - 1
2 - 1
2 - 2
3 - 1
3 - 2
3 - 3
4 - 1
4 - 2
4 - 3
4 - 4

リスト内包表記は糖衣構文なので適切な場合にのみ利用したほうが良いでしょう。

フィルタ

フィルタは内包表記のためのある種のガードと考えることができます。フィルタされた値が falsenil を返す場合には、最終的なリストから取り除かれます。ある範囲をループし偶数のみ気にすることにしましょう。値が偶数かどうかはIntegerモジュールの is_even/1 を用います。

import Integer
iex> for x <- 1..10, is_even(x), do: x
[2, 4, 6, 8, 10]

ジェネレータ同様、複数のフィルタを使うこともできます。先ほどの範囲を広げ、偶数かつ3で割り切れる値のみをフィルタしましょう。

import Integer
iex> for x <- 1..100,
...>   is_even(x),
...>   rem(x, 3) == 0, do: x
[6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]

:into の使用

リストではなく、他のものを生成したい場合はどうすれば良いでしょうか。 :into オプションを使えば可能です! 一般的な目安として、 :intoCollectable プロトコルを実装している構造体を指定できます。

:into を用いて、キーワードリストからマップを作成しましょう:

iex> for {k, v} <- [one: 1, two: 2, three: 3], into: %{}, do: {k, v}
%{one: 1, three: 3, two: 2}

ビット文字列 (bitstring) は列挙可能 (enumerable) なので、リスト内包表記と :into を用いて文字列を作成することができます:

iex> for c <- [72, 101, 108, 108, 111], into: "", do: <<c>>
"Hello"

以上です! リスト内包表記は簡潔なやり方でコレクションを反復する、容易な方法です。

間違いを報告したい、あるいはこのレッスンに貢献したい? このレッスンをGitHubで編集しよう!