Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

Elixirで遊んでみる(6)

9章は2ページしかない。Elixirには型みたいなものが2つのレベルである。 プリミティブなデータ型と、モジュールとそれが提供する関数が規定するレベルでの型だ。 あるモジュールはプリミティブなデータ型に加えてある構造を要求する。

例えば、ListもKeywordも実体としてのデータの型は単なるリストだ。 しかし、「アトムと値のペアからなるタプルのリスト」という追加の構造を持つことでKeywordモジュールの関数は成り立っていて、 利用するプログラマはそれを含めてKeywordをListとは違う型のように受け取っている。 つまり、OOP言語で言えば、プリミティブな文法レベルの上に標準クラスライブラリの話がはじまるが、 それに当たるAPIの話がこの後に来て、プログラマとしてはまさにそれに習得することが重要なのだろう。

というわけで10章はコレクションAPIの話になる。

最初はEnum。だいたい、RubyのEnumerableにありそうな関数がある。mapとかfilterとかsortとか。joinもここにある。 all?とかempty?みたいな?が関数名につくのもRuby style。ただし、include?はmember?になってる。 当然、reduceもここにある。

さて、P.102には練習問題がある。all?を実装してみろと。挑戦してみよう。こんな感じかな?

defmodule MyEnum do
  def all?([], _), do: true
  def all?([first|rest], f) when not f.(first), do: false
  def all?([_|rest], f), do: all?(rest, f)
end

えっ?ダメなの?

iex(117)> c "ch10ex5.exs"

== Compilation error in file ch10ex5.exs ==
** (CompileError) ch10ex5.exs:3: invalid expression in guard, anonymous call is not allowed in guards. To learn more about guards, visit: https://hexdocs.pm/elixir/patterns-and-guards.html
    (stdlib 3.14) lists.erl:1358: :lists.mapfoldl/3
** (CompileError)  compile error
    (iex 1.11.2) lib/iex/helpers.ex:200: IEx.Helpers.c/2

ガード節の中で無名関数は使えないと。えー、マジカヨ。

defmodule MyEnum do
  def all?([], _), do: true
  def all?([first|rest], f) do
    if f.(first) do
      all?(rest, f)
    else
      false
    end
  end
end

ダサい。猛烈にダサい。何かがおかしい。コレジャナイハズ。うーむ・・・

次は、Stream。遅延評価したいときはEnumじゃなくてStreamを使う。

自分でストリームを作ることも出来る。以下はcycle, repeatedly, iterateの例。

iex> Stream.cycle([1, 2, 3]) |> Enum.take(5)
[1, 2, 3, 1, 2]
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(5)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987, 0.5014907142064751,
 0.311326754804393]
iex> Stream.iterate(1, &(&1 * -1)) |> Enum.take(5)
[1, -1, 1, -1, 1]

これはわかりやすい。

Stream.unfoldは、前のイテレーションのステータスによって次の値が作られるようなものだ。 何に使うんだろう。とりあえず、本にはフィボナッチの例が出ている。

iex> Stream.unfold({0,1}, fn {f1, f2} -> {f1, {f2, f1+f2}} end) |> Enum.take(15)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

Stream.resourceはやりたいことはわかるけど、なかなか難しい。 3つの関数を与える。初期化、値の取得、終了処理の3つ。そうすると、値が取り出せるストリームが作られる。 例えば、ファイルを1行ずつ読むとしたら、(1)ファイルのオープン(2)1行リード(3)ファイルのクローズ だ。 実際、これを作ることになったらかなり悩みそう。

この章の最後は、内包表現。理解できるかなあ・・・。こんな感じだ。

iex> first8 = Enum.to_list 1..8
[1, 2, 3, 4, 5, 6, 7, 8]
iex> for x <- first8, y <- first8, x >= y, rem(x*y, 10) == 0, do: {x, y}
[{5, 2}, {5, 4}, {6, 5}, {8, 5}]

forの後ろに複数のパターンマッチを書いてよい。その後ろにフィルタが来る。パターンマッチやフィルタでは、それ以前のマッチの結果をつかっていい。

内包表現の返り値はリストに限らない。into: で変更できる。

iex> for x <- ~w{ USA Japan Luxembourg }, into: %{}, do: {x, String.length(x)}
%{"Japan" => 5, "Luxembourg" => 10, "USA" => 3}