😊

【Elixir v1.12】tap/2, then/2が便利

2021/06/19に公開1

Elixirのv1.12が5/19にリリースされておよそ1か月ほど経ちました。v1.12では新しく Kernel.then/2, Kernel.tap/2 という関数が追加されています。こちらが地味に便利なので解説してみます。

https://elixir-lang.org/blog/2021/05/19/elixir-v1-12-0-released/

then/2

then/2は第1引数の値を第2引数の関数に渡し、その結果を返します。

  iex> 1 |> then(fn x -> x * 2 end)
  2

pipeをつなげていく中で引数の順番を変えたい場合に有用です。

  iex> "some text" |> then(&File.write!("out.txt", &1))
  :ok

これまで then/2 がない場合には、次のように書く必要がありました。thenを使った方がスッキリして良いですね。

  iex> "some text" |> (&File.write!("out.txt", &1)).()
  :ok

tap/2

tap/2then/2同様、第1引数の値を第2引数で指定した関数に渡して実行しますが、第1引数に渡した値自体をそのまま返します。

  iex> tap(1, fn x -> x + 1 end)
  1

pipeの間にログ出力を挟むなど、pipeの中で出力を変えずに別の処理を行いたい場合に便利です。

some_func()
|> tap(&Logger.info/1)
|> other_func()

IO.inspectのように「pipeの流れ自体は変えたくないけど、処理を挟みたい」というケースが出てきたら tap/2 を使うと良さそうです。

コード例

公式ブログに載っているコードブロックが分かりやすいのでこちらにも置いておきます。tap/2, then/2 を使った方が明らかにスッキリしていますね。

tap/2, then/2を利用した場合

"hello world"
|> tap(&IO.puts/1)
|> then(&Regex.scan(~r/\w+/, &1))

tap/2, then/2を利用しない場合

"hello world"
|> (fn x ->
      IO.puts(x)
      x
    end).()
|> (&Regex.scan(~r/\w+/, &1)).()

まとめ

Elixir v1.12から導入された Kernel.then/2, Kernel.tap/2 を紹介しました。公式ブログ内の記述でも、

  • tap/2とthen/2はどちらもマクロとして実装されている
  • Erlang/OTP 24ではコンパイラの改良により、中間の無名関数が最適化されている

とのことですので、無名関数をthenやtapを利用して繋げるのはパフォーマンス的にも問題なさそうです。ぜひ活用ください。

参考

https://elixir-lang.org/blog/2021/05/19/elixir-v1-12-0-released/

https://medium.com/blackode/two-new-life-saving-functions-worth-knowing-elixir-1-12-tap-then-b0120fcfb93f

Discussion

adamadam
"hello world"
|> tap(&IO.puts/1)
|> then(&Regex.scan(~r/\w+/, &1))

この例はiexではありませんが、v1.12からiexでも|>を使えばv/0と同じように前のコマンドの結果を引き込んでくれるので、ちょっと煩くなりますがpipeもiexにコピペできるようになりました。

iex(1)> "hello world"
"hello world"
iex(2)> |> tap(&IO.puts/1)
hello world
"hello world"
iex(3)> |> then(&Regex.scan(~r/\w+/, &1))
[["hello"], ["world"]]