23
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ElixirAdvent Calendar 2019

Day 6

みんな大好きパイプラインの正体を追え!

Last updated at Posted at 2019-12-05

この記事は Elixir Advent Calendar 2019 6日目の記事です

昨日は @sym_num さんで Elixirで並列Lispインタプリタ、コンパイラを作るお話でした!


Elixirの大好きなところを言ってみてって言われたら多すぎて困っちゃうと思いますが、その中でも パイプライン演算子 をあげる錬金術士さんは多いんじゃないでしょうか :question:

今日はそんなみんな大好きパイプの正体を追ってみたいと思います :eyes:

そもそもパイプって?

参考: https://elixirschool.com/ja/lessons/basics/pipe-operator/

|> ←こいつです

前の式の結果を次の関数呼び出しの第一引数に渡せるという便利なやつです。

foo(bar(baz(new_function(other_function()))))
# ↓
other_function() |> new_function() |> baz() |> bar() |> foo()

よくありがちな関数の結果を次の関数に渡す・・・というのをスマートにおしゃれに書くことができます:ribbon:

このパイプライン演算子とElixirの特徴の関数型言語というのがすごいマッチしていて、気持ちよくプログラミングすることができますね :sparkles:

早速パイプの実装を発見した我々だが・・・?

というわけで、 GithubにあるElixirのリポジトリを探検してみたところ、すぐに |> を見つけることができました :eyes:

lib/elixir/lib/kernel.ex
  defmacro left |> right do
     [{h, _} | t] = Macro.unpipe({:|>, [], [left, right]})
     fun = fn {x, pos}, acc ->
       Macro.pipe(acc, x, pos)
     end

     :lists.foldl(fun, h, t)
   end

シンプル!!けど難しい!!

まずは少しずつ解読をすすめていくことにします。

defmacro left |> right do

Elixirでは defmacro を使うことで、マクロを定義して、メタプログラミングすることができるようです。

参考: https://elixirschool.com/ja/lessons/advanced/metaprogramming/

そして defmacro に渡ってくる値は構文木(AST)になるようです。

動作を確認するために適当に以下のようなコードを書いてみました

main.exs
defmodule Sample do
  defmacro left | right do
    IO.inspect left
    IO.inspect right
    left
  end
end

defmodule Main do
  require Sample
  import Sample

  def main do
    div(100, 5) | div(2)
  end
end

Main.main

# $ elixir main.exs
# {:div, [line: 14], [100, 5]}
# {:div, [line: 14], [2]}

たしかに leftright には構文木が渡されていますね :thinking:
興味深いのは div(2) という普通だったら、怒られるような関数呼び出しを書いていても特に怒られずに実行できているところです。
このコードではマクロに構文木を渡しているだけで関数の評価までいっていないので怒られていないって感じでしょうか :thinking:

[{h, _} | t] = Macro.unpipe({:|>, [], [left, right]})

Macro.unpipe/1lib/elixir/lib/macro.ex に実装があります

lib/elixir/lib/macro.ex
  def unpipe(expr) do
    :lists.reverse(unpipe(expr, []))
  end

  defp unpipe({:|>, _, [left, right]}, acc) do
    unpipe(right, unpipe(left, acc))
  end

  defp unpipe(other, acc) do
    [{other, 0} | acc]
  end

こちらもシンプルですね!!

試しに iex を作って直接実行してみました

iex> quote do: 100 |> div(5) |> div(2)
{:|>, [], [{:|>, [], [100, {:div, [], [5]}]}, {:div, [], [2]}]}

iex> Macro.unpipe(quote do: 100 |> div(5) |> div(2))
[{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}]

どうやらパイプラインの構文木をシンプルなタプルのリストに変換してくれるみたいです。

タプルの2番目の0は前の要素が何番目の引数に入るかを示しているようです。
{{:div, [], [5]}, 0} だったらその前の要素の 100 が 0番目(第1引数) になるということでみたいですね!

fun = fn {x, pos}, acc ... :lists.foldl(fun, h, t)

ここまで来たら残りは一気に行きます!

     fun = fn {x, pos}, acc ->
       Macro.pipe(acc, x, pos)
     end

     :lists.foldl(fun, h, t)

foldl はReduceみたいなもので、配列の左側から渡された関数を適用していくみたいですね。

ここでは、unpipeして簡略されたASTに Macro.pipe/3 を適用していってます

Macro.pipe/3 を覗いたところ、今回主に実行されそうなのは以下の部分でした。

lib/elixir/lib/macro.ex

  def pipe(expr, {op, line, args} = op_args, integer) when is_list(args) do
    cond do
      is_atom(op) and Identifier.unary_op(op) != :error ->
        raise ArgumentError,
              "cannot pipe #{to_string(expr)} into #{to_string(op_args)}, " <>
                "the #{to_string(op)} operator can only take one argument"

      is_atom(op) and Identifier.binary_op(op) != :error ->
        raise ArgumentError,
              "cannot pipe #{to_string(expr)} into #{to_string(op_args)}, " <>
                "the #{to_string(op)} operator can only take two arguments"

      true ->
        {op, line, List.insert_at(args, integer, expr)}
    end
  end

{op, line, List.insert_at(args, integer, expr)} はい、この部分ですね。

構文木のタプルの3番目は引数の配列になるのですが、そちらに差し込んでいますね。

なので最終的に 100 |> div(5) の構文木は {:div, _, [100, 5]} となります!

これは div(100, 5) の構文木と一緒ですね :sparkles:

iex> quote do: div(100, 5)
{:div, [_], [100, 5]}

まとめ

というわけで、パイプライン演算子 |> の正体はただのマクロでした!!

パイプライン演算子だけではなく、Elixirではほとんどの演算子がマクロによって定義されているみたいですね。

中身を覗いてすごい読みやすく実装されていて驚きました。

こんなコードを書いていけるようになりたいですね :sparkles:


明日は @takasehideki さんの「Erlang Ecosystem Foundationの紹介」です!!

楽しみですね:musical_note:

23
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?