DEV Community

gumi TECH for gumi TECH Blog

Posted on • Edited on

3 1

Elixir入門 17: 内包表記

本稿はElixir公式サイトの許諾を得て「Comprehensions」の解説にもとづき、加筆補正を加えて、Elixirで使える内包表記の構文についてご説明します。

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

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

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

iex> for n <- [1, 2, 3, 4, 5], do: n * n
[1, 4, 9, 16, 25]
Enter fullscreen mode Exit fullscreen mode

ジェネレータ

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

iex> for n <- 1..5, do: n * n
[1, 4, 9, 16, 25]
Enter fullscreen mode Exit fullscreen mode

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

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"]
Enter fullscreen mode Exit fullscreen mode

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

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]
Enter fullscreen mode Exit fullscreen mode

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

iex> for i <- [:a, :b, :c], j <- [1, 2], do:  {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
Enter fullscreen mode Exit fullscreen mode

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

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]
]
Enter fullscreen mode Exit fullscreen mode

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

フィルタ

ジェネレータから取り出す値は、フィルタで絞り込めます。フィルタとして定めるのは関数です。戻り値が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]
Enter fullscreen mode Exit fullscreen mode

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

iex> import Integer
Integer
iex> for n <- 0..10, is_even(n), do: n
[0, 2, 4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

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

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

内包表記を用いると、EnumStreamモジュールの関数を使う処理がずっと簡潔に書けます。さらに、ジェネレータやフィルタをいくつも加えることができるのです。つぎのコードはディレクトリのリストからファイルのパスを探し、標準ファイルであることを確かめたうえで、それぞれのサイズをリストで出力します(図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
Enter fullscreen mode Exit fullscreen mode
$ elixir example.exs
6
7
Enter fullscreen mode Exit fullscreen mode

図001■ディレクトリ内のファイル

elixir_17_001.png

ビットストリングジェネレータ

ビットストリングもジェネレータで扱えます。ビットストリングのストリームを解析するのに便利です。たとえば、ピクセルの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]
]
Enter fullscreen mode Exit fullscreen mode

:intoオプション

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

iex> for c <- [104, 101, 108, 108, 111], into: "", do: <<c>>
"hello"
Enter fullscreen mode Exit fullscreen mode

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

iex> for <<char <- " to be, to be, ten made to be ">>, char != ?\s, into: "", do: <<char>>
"tobe,tobe,tenmadetobe"
Enter fullscreen mode Exit fullscreen mode

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

iex> for {key, val} <- %{a: 1, b: 2, c: 3}, into: %{}, do: {key, val * val}
%{a: 1, b: 4, c: 9}
Enter fullscreen mode Exit fullscreen mode

IO.stream/2は、入力をIO.Streamにして返します。そして、IO.Streamの実装するプロトコルは、EnumerableCollectableです。つぎのコードは、キーボードから入力した英字を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  #<- 出力
Enter fullscreen mode Exit fullscreen mode

Elixir入門もくじ

番外

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

While many AI coding tools operate as simple command-response systems, Qodo Gen 1.0 represents the next generation: autonomous, multi-step problem-solving agents that work alongside you.

Read full post

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay