Elixir

Elixir: パイプ演算子|>について

パイプ演算子|>については、gumi TECH Blog「Elixir入門 10: EnumとStream」の「パイプ演算子」で説明されています。本稿は、そのおさらいもしつつ、公式ドキュメントの情報などを補います。


パイプライン演算

パイプ演算子|>による演算は、パイプライン演算と呼ばれることもあります。左辺の式の値を右辺の関数の第1引数として渡します。


左辺 |> 右辺


たとえば、つぎのコードはList.flatten/1でリスト内の入れ子を平坦化します。左辺が第1引数ですので、右辺の関数の引数には加えません。

iex> list = [1, [[2], 3]] |> List.flatten 

[1, 2, 3]

引数がふたつあるときは、関数には第2引数をかっこ()でくくって渡します。この場合のかっこは省けません(省くと警告が示されます)。

iex> square = list |> Enum.map(fn(x) -> x * x end)      

[1, 4, 9]

引数がふたつ以上あるときも、同じように第1引数を省いて、第2引数以降をかっこ()に加えてください。

iex> sum = square |> Enum.reduce(0, fn(x, acc) -> x + acc end)

14

上記3つの演算をパイプ演算子なしに1行で書くと、つぎのようになります。入れ子になり、しかも内側から演算されますので、読みにくいコードです。

iex> sum = Enum.reduce(Enum.map(List.flatten([1, [[2], 3]]), fn(x) -> x * x end), 0, fn(x, acc) -> x + acc end)

14

パイプ演算子を使えば、左から演算の順に書けますので、わかりやすいでしょう。

iex> [1, [[2], 3]] |> List.flatten |> Enum.map(fn x -> x * x end) |> Enum.reduce(0, fn(x, acc) -> x + acc end) 

引数に関数があるときは、キャプチャ演算子&を使うとさらに短く書けます(「Elixir入門 08: モジュールと関数」「関数のキャプチャ」参照)。

iex> [1, [[2], 3]] |> List.flatten |> Enum.map(&(&1 * &1)) |> Enum.reduce(0, &+/2)

14


パイプ演算子で注意すること

Elixir公式ドキュメントの|>/2の項にはふたつの注意が示されています。


演算子の優先順位

ひとつは、演算子の優先順位に関わるかっこ()づけです。つぎのコードはエラーになります。

String.graphemes "Hello" |> Enum.reverse

演算子の優先順位にしたがってつぎのように解釈され、Enumerableプロトコルがバイナリに対して定められていないからです。

iex> String.graphemes("Hello" |> Enum.reverse)

** (Protocol.UndefinedError) protocol Enumerable not implemented for "Hello"

したがって、つぎのように優先順位をかっこで示さなければなりません。

iex> String.graphemes("Hello") |> Enum.reverse

["o", "l", "l", "e", "H"]

もっとも、せっかくパイプ演算子を使うのですから、つぎのように書くほうがよいでしょう。

iex> "Hello" |> String.graphemes |> Enum.reverse

["o", "l", "l", "e", "H"]


無名関数に用いるとき

もうひとつの注意は、パイプ演算子を無名関数に用いるときです(関数のキャプチャについても同じ)。

iex> func = fn(str) -> str |> String.graphemes |> Enum.reverse end

#Function<6.127694169/1 in :erl_eval.expr/5>

関数名のあとのドット(.)を忘れてはいけませんし、引数がひとつでもかっこ()は省けません。

iex> "Hello" |> func.()

["o", "l", "l", "e", "H"]

けれど、パイプ演算子を使わなくてもこれは同じです。

iex> func.("Hello")

["o", "l", "l", "e", "H"]

少し気をつける点はあるものの、見やすいコードを書くために、パイプ演算子は役立つでしょう。ところで、『プログラミングElixir』(原書『Programming Elixir 1.6』)の著者Dave Thomas氏はこのパイプ演算子がお気に入りのようで、書籍のサブタイトルにつぎのように使われています。


Functional

|> Concurrent

|> Pragmatic

|> Fun