どうも、靖宗です。
今回は「Comprehensions」。もう単語の意味もよく分かりません・・・(包容力?包含?コンプリヘンション?)
どうやらComprehensionsはリストなどの再帰ループの糖衣構文のようです。
今まで出てこなかったfor
について言及されてます。
もはやここまで来るとなじみ深いfor
が出てくる方が不気味です。
さっそく例を見ていきます。
iex> for n <- [1, 2, 3, 4], do: n * n [1, 4, 9, 16]
あえてEnumで書くならEnum.map([1, 2, 3, 4], fn x -> x * x end)
といったところでしょうか。
Comprehensionはgenerators
、filters
、collectables
の3要素でできているようです。
Generators and filters(ジェネレータとフィルター)
先ほどの例でいうと、n <- [1, 2, 3, 4]
がジェネレータに該当するようです。
ここではリストが書かれてますが、rangeなども使える様です。
iex> for n <- 1..4, do: n * n [1, 4, 9, 16]
キーワードリストなどとのパターンマッチも使用可能
iex> values = [good: 1, good: 2, bad: 3, good: 4] iex> for {:good, n} <- values, do: n * n [1, 4, 16]
パターンマッチだけで色々フィルタリングできそうな気はしますが、より細かい条件付けなどの場合はフィルターの方が有用かもしれません。
フィルターはジェネレータの後にかくようです。
iex> multiple_of_3? = fn(n) -> rem(n, 3) == 0 end iex> for n <- 0..5, multiple_of_3?.(n), do: n * n [0, 9]
フィルターに記載した関数がtrue
の時のみの結果となってます。
フィルターの関数がfalse
かnil
以外の時の値を採用するようです。
このジェネレータやフィルターを用いるとEnum
やStream
よりも簡潔に書けるとのことですが、どうなんでしょうか・・・
ケースバイケースですかね。とりあえずサンプルを見ます。
dirs = ['/home/mikey', '/home/james'] for dir <- dirs, file <- File.ls!(dir), path = Path.join(dir, file), File.regular?(path) do File.stat!(path).size end
('ω')。o(????????????)
|>
で紡いでいったほうが分かりやすそうな気がしますが、丁寧に1個1個みていくとdir <- dirs
とfile <- File.ls!(dir)
がジェネレータに該当し、path = Path.join(dir, file)
とFile.regular?(path)
がフィルターに該当するといったところでしょうか。
path = Path.join(dir, file)
に関してはタダの変換やんと言いたくなりますが、そういうもんなんでしょう。
複数ジェネレータを書いた場合は後に書いた方が先に回る仕組みみたいです。
iex> for i <- [:a, :b, :c], j <- [1, 2], do: {i, j} [a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
他の言語でfor
がネストしてるイメージでしょうか。
あとはcomprehension内部で使用した変数などは外部に影響しないとのこと。これはまぁそうですよねという話な気はします。
Bitstring generators
Bitstring generatorsなるものの説明が。bitstringのストリームで役にたつで!とのこと。サンプルを見ます。
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>> iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b} [{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]
bitstringできたらこう書きましょうって感じでしょうか。
string系列はじゃっかんヤヤコシイので一回復習しないとあかんかな。
The :into option
基本的に先ほどまでの例ではcomprehensionはリストを返してましたが:into
オプションを利用することでmapや文字列に放り込めるそうです。
とりあえずサンプルを見ていきます。
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>> "helloworld"
bitstringをスペースであるかの成否でフィルタリングして文字列に放り込むといったところでしょうか。
おそらくinto:""
で文字列に対する変換の処理が暗示的になされている気がします。
mapへの:into
オプションの例。
iex> for {key, val} <- %{"a" => 1, "b" => 2}, into: %{}, do: {key, val * val} %{"a" => 1, "b" => 4}
ここでもinto
のオプションで変換先がmapである事が明示されています。
おそらくココでプロトコルのような実装がなされていて、暗示的にリストからmapへの変換がなされている?
Collectable
プロトコルに放り込まれているようなので一応見てみる。
defimpl Collectable, for: List do def into(original) do ...
やっぱりそうっぽい。
あまり深入りせずサンプルのようなケースを覚えて行った方が生産的でしょうか。
リストはEnumerables
なので、遅延評価する場合は:into
オプションでストリームに放り込むこともできるみたい。
iex> stream = IO.stream(:stdio, :line) iex> for line <- stream, into: stream do ...> String.upcase(line) <> "\n" ...> end
ただし上のサンプルは実行すると永遠に終わらないと思います。
このへんは使っていくウチに慣れるか、リストとして結果が受けれるので結局パイプ演算子で垂れ流すかするのが良さそうな気はします。