プログラミングElixir第10章~コレクションの処理

概要

  • Enumモジュール
  • Streamモジュール
  • Collectableプロトコル
  • 内包表記 (comprehension)

前章までに リスト と マップ というコレクションとして振る舞う型を見てきた。他にもいくつもあるらしい。

共通的なコトは、コレクションが持っている要素?に対して、繰り返し処理ができるということ。 `Enumerableプロトコルを実装しているといえる` という表現をしている。

Streamモジュールは.. 少し先に見たことがある、パイプ演算子を挟んで処理の並列化で前段の終了を待たずにデータを流し込む、遅延処理してくれるようなもの...かな.

Enum - コレクションの処理

詳しくは Elixir schoolのEnumの章を読むとして、おおよそ以下の機能をもつ:

  • コレクションをリストに変換
  • コレクションを結合する
  • 与えられたコレクションの各要素に、何らかの関数を適用した結果を要素とする新しいコレクションを生成する
  • 位置、基準によって要素を選択(フィルタリング)
  • 要素をソート、比較する
  • コレクションを分割する
  • 要素を連結する
  • 術後演算
  • コレクションをマージする

・・・・

やってみよう

Enum.all を 作る

期待値

iex(5)> Enum.all?( [2,4,6], &(rem(&1,2)==0) )
true

試行錯誤の結果

defmodule MyEnum do
  defp _all([_], false, _), do: false
  defp _all([_], nil, _), do: false
  defp _all([], _, _), do: true
  defp _all([h|t], _, f), do: _all(t, f.(h), f)

  def all?([h|t], f), do: (
    _all(t, f.(h), f)
  )
end

おけ

iex(7)> MyEnum.all?( [2,4,6], &(rem(&1,2)==0) )
true

Enum.eachをつくる

期待値

iex(9)> Enum.each(["some", "example"], fn x -> IO.puts(x) end)
some
example
:ok
defmodule MyEnum do
  defp _each([h], func), do: func.(h)
  defp _each([h|t], func), do: (
    func.(h)
    _each(t,func)
  )

  def each([h|t], func), do: (
    _each([h|t], func)
  )
end
iex(18)> MyEnum.each(["some", "example"], fn x -> IO.puts(x) end)
some
example
:ok

Enum.filter を 作る

defmodule MyEnum do
  defp _filter([], _func, result), do: result
  defp _filter([h|t], func, result), do: (
    if func.(h) do
      _filter(t, func, result++[h])
    else
      _filter(t, func, result)
    end
  )

  def filter([h|t], func), do: (
    _filter([h|t], func, [])
  )
end
iex(31)> MyEnum.filter([1, 2, 3,4,5,6,7], fn x -> rem(x, 2) == 0 end)
[2, 4, 6]

不具合

誤: _filter(t, func, [result|h])
正: _filter(t, func, result++[h])

resultがリストであることを期待して記述したつもり。縦棒は要素を区切るものだったから 誤りではリストと新しいスカラ値の2要素のリストが生成された。

リストに要素を追加する場合は `++` 演算子を使う。

MyEnum.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
iex(22)> MyEnum.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
[[] | 2]

Enum.split を 作る

TODO: あとで作る? リスト、番号を引数に持つ。

ローカル関数で結果のタプルを引数に渡して呼び出す。要素数を自然数で受けるようにして、前からいくつめかを認識させ、1ずつ減らしてヘッドから要素を取り出していく。結果のタプルを都度更新していけば結果が得られるだろう。

Enum.take を 作る

TODO: あとで作る?

splitと似たような実装。結果は要素ひとつにすれば同じように作れる?(共通化は?)

ストリーム~遅延列挙

Enumはデータすべてを処理しきる必要があり、パイプ演算子で結合した段それぞれで処理の完了を待つ。ストリーム対応であれば、処理の完了を待たずに逐次出力されたデータを次の段に渡していく。最終的にListに入れる場合などは、すべてのデータが入力されるのを待つ処理が入る。

・・・・そんな感じ?