読者です 読者をやめる 読者になる 読者になる

Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

急いで覚えるElixir: Enumerable編

ElixirのEnumerable

前回の記事から続いて、今回はElixirで利用する基本的な制御構文について学んでいきます。

Keyword list

多くの関数型プログラム言語では、2要素のtupleによって関連付けられたデータ構造を表現します。 Elixirでは、最初の要素がAtomであるTupleのListのことをKeyword listと呼びます。

iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]
true
iex> list[:a]
1

キーが重複している場合、先頭に格納された値が優先的に読まれます。

iex> new_list = [a: 0] ++ list
[a: 0, a: 1, b: 2]
iex> new_list[:a]
0

Keyword listは以下の重要な性質を持ちます。

  • キーはAtomでなければならない
  • キーはユーザーが指定した順に並ぶ
  • キーが重複可能である

Map

Key value storeが必要なときは、迷わずMapを使うといいでしょう。 Mapは %{} 構文により作成します。

iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil

Keyword list との違いは以下の点です。

  • MapはKeyの型を限定しない(Atomでなくてもいい)
  • 順序構造がない

Dict

Elixirでは、Keyword listとMapはいずれもDictionaryとして分類されます。 Keyword listとMapは一見rubyのHashのようですが、rubyのHashのように後から値を追加するために、[]を利用することは出来ません。 新たに値を追加するには、次のように、Dict.put/3を利用します。

iex> keyword = []
[]
iex> map = %{}
%{}
iex> Dict.put(keyword, :a, 1)
[a: 1]
iex> Dict.put(map, :a, 1)
%{a: 1}

Enumerable

ElixirではEnumerableがサポートされていて、ListとMapがEnumerableに該当します。

iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end)
[2, 12]

Enumモジュールは、Enumerableの要素を変形・ソート・グループ化・フィルター・取得するための多数の関数を提供していて、Elixirで書かれたコードでよく登場するモジュールです。 Enumモジュールは多数のデータ型をサポートする設計で作られているため、そのAPIはデータ型に依存しない関数に限られています。 より具体的な操作のためには、そのデータ型に対応したモジュールを使用しないといけない場合もあります。例えば、Listの中の特定の位置に要素を挿入したい場合、 Listモジュールで定義されたList.insert_at/3関数を使用します。

パイプ演算子

上記の例の|>記号はパイプ演算を表しています。この演算では、Unix|演算子のように左辺を評価した後、右辺の第一引数として渡します。 パイプ演算子は複数の関数をまたがるデータの流れをわかりやすく記述するために用いられます。

iex> Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
7500000000

パイプを使うと上のような煩雑な処理を次のように書き換えることが出来ます。

iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
7500000000
iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
7500000000

ここでEnumStreamという2つのモジュールが新たに出てきます。 その違いは、EnumはEagerに演算を行い、StreamはLazyな演算を行うことが出来ます。 例えば上の例では、StreamEnumモジュールに渡された時に演算が初めて実行されることになります。 Lazyに評価する事の利点は、例えばStreamを実際に演算しなくていい条件がある場合、その演算に利用する巨大なオブジェクトをはじめから保持する必要が無いことにあります。

Generator

ElixirではEnumerableに対してループ処理をしたり、別のListにMapするといった処理を頻繁に使います。 例えば、IntegerのListの各要素を2乗したMapは以下のように書くことができます。

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

n <- [1, 2, 3, 4]の部分をGeneratorと呼びます。 Generatorの右辺には、Enumerbleを指定することができます。 また、Generatorの左辺にパターンマッチを使うこともできます。 例えば、キーが:good:badのいずれかであるKeyword Listに対して、:goodの値のみの2乗を計算したい場合、以下のようにします。

iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]

まとめ

ElixirのEnumerableについて学ぶことが出来ました。 より詳しく知りたい方は、以下の公式ドキュメントからよりカッコイイ書き方を知ることが出来ます。(外部サイトへ飛びます。)