パイプライン演算子のはなし

  • 40
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

|> これです。

left |> rightleftright に第一引数として渡されます。

たとえば、「リストを平坦化して各項を2倍する処理」は、

[1, [2], 3] |> List.flatten |> Enum.map(&(&1 * 2))

このように書けます。便利ですね。

これで終わってしまうとあれなので、もうちょっと続きます。

成否を表すタプルが返り値の場合

ここで次のような関数を考えてみましょう。
リストの先頭を取り出す関数です。(あくまでも例です)

def head(list) do
  case list do
    [] -> { :error, :empty_list } 
    [h|_] -> { :ok, h }
  end
end

こんな感じの

  • { :ok, value } というタプルで結果を返す
  • エラーを返す

みたいな関数は |> のチェインの途中にうまく組み込むことができません。
こういう返り値はErlangの関数ではよくある形です。

こういう関数を扱おうと思うと、

case foo(arg) do
  { :ok, result0 } ->
    case bar(result0) do
      { :ok, result1 } ->
        case baz(result1) do
          { :ok, result2 } ->
            result2
          error ->
            error
        end
      error ->
        error
    end
  error ->
    error
end

こんな感じで case のネストがどんどん深くなっていきます。

ちょっとつらいのでいい感じに |> でこういった関数を扱いたい。

そんなときはこれ、if_ok/1

  defmacrop if_ok(expr, call) do
    quote do
      case unquote(expr) do
        {:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
        other -> other
      end
    end
  end

今回は実装の詳細には立ち入りませんが、気になる人は、Kernel.|>, Macro.unpipe, Macro.pipe あたりが参考になると思います。

これを用いると、さっきの例がこうなります。

case foo(arg) |> if_ok(bar) |> if_ok(baz) do
  { :ok, result } -> 
    result
  error -> 
    error
end

関数を if_ok/1 でくるんでおくと、左辺から

  • { :ok, value } がきた場合は value を引数として関数を実行するしてその返り値を次に渡す
  • それ以外のものがきた場合はそれをそのまま次に渡す

みたいな感じになります。

まとめ

パイプライン演算子は大変記述が簡潔になる便利機能です。

そしてそのちょっとした応用を紹介してみました。
パイプライン演算子はマクロとして実装されているので if_ok/1 のような変なことができます(おすすめはしませんが)。

Elixirのパイプライン演算子についての議論は このスレ がおすすめです。

ちなみにパイプライン演算子はF#さんからのいただきものです。

明日は @ma2ge さんです!

この投稿は Elixir Advent Calendar 201311日目の記事です。