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

gumitech

gumi TECH

Posted on April 2, 2019

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

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

パイプライン演算

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

左辺 |> 右辺

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

iex> list = [1, [[2], 3]] |> List.flatten 
[1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

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

iex> square = list |> Enum.map(fn(x) -> x * x end)      
[1, 4, 9]
Enter fullscreen mode Exit fullscreen mode

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

iex> sum = square |> Enum.reduce(0, fn(x, acc) -> x + acc end)
14
Enter fullscreen mode Exit fullscreen mode

上記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
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

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

演算子の優先順位

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

String.graphemes "Hello" |> Enum.reverse
Enter fullscreen mode Exit fullscreen mode

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

iex> String.graphemes("Hello" |> Enum.reverse)
** (Protocol.UndefinedError) protocol Enumerable not implemented for "Hello"
Enter fullscreen mode Exit fullscreen mode

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

iex> String.graphemes("Hello") |> Enum.reverse
["o", "l", "l", "e", "H"]
Enter fullscreen mode Exit fullscreen mode

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

iex> "Hello" |> String.graphemes |> Enum.reverse
["o", "l", "l", "e", "H"]
Enter fullscreen mode Exit fullscreen mode

無名関数に用いるとき

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

iex> func = fn(str) -> str |> String.graphemes |> Enum.reverse end
#Function<6.127694169/1 in :erl_eval.expr/5>
Enter fullscreen mode Exit fullscreen mode

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

iex> "Hello" |> func.()
["o", "l", "l", "e", "H"]
Enter fullscreen mode Exit fullscreen mode

けれど、公式ドキュメントも述べているとおり、パイプ演算子を使わない場合でも、ドット(.)とかっこ()は必要です。うっかり忘れないように、という注意でしょう。

iex> func.("Hello")
["o", "l", "l", "e", "H"]
Enter fullscreen mode Exit fullscreen mode

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

Functional
|> Concurrent
|> Pragmatic
|> Fun

💖 💪 🙅 🚩
gumitech
gumi TECH

Posted on April 2, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related