🎉

【Elixir】v1.14でリリースされたdbg/2が想像以上に便利だった

2022/09/03に公開約3,800字

2022/09/01、Elixirのv1.14がリリースされました🎉

以下の公式ブログにv1.14のリリース機能がまとまっています。

https://elixir-lang.org/blog/2022/09/01/elixir-v1-14-0-released/

目玉機能として dbg/2 の導入があるのですが、リリース前に見ていた肌感だと「便利なIO.inspect」ぐらいにしか捉えていなかったのですが、ブログ内にある「IEx+dbg」の挙動が使い勝手が良さそうでしたので、改めて dbg/2 の挙動を紹介してみます。

dbgを引数なしで使う

まずは基本的な使い方です。引数なしで dbg を実行すると、該当行で束縛している変数の情報を確認できます。

sample.exs
x = 1
a = %{b: 1, c: 2}
uri = URI.parse("https://example.com")

dbg()
$ elixir sample.exs

[sample.exs:5: (file)]
binding() #=> [
  a: %{b: 1, c: 2},
  uri: %URI{
    scheme: "https",
    authority: "example.com",
    userinfo: nil,
    host: "example.com",
    port: 443,
    path: nil,
    query: nil,
    fragment: nil
  },
  x: 1
]

dbgをpipeにつなげて使う

dbg/2 をpipeにつなげて実行すると、pipeの各行の処理結果を確認できます。これが強力で、IO.inspect で確認していた作業はすべて dbg で行えるようになりました。

sample_pipe.exs
1..10
|> Enum.map(fn x -> x * x end)
|> Enum.filter(fn x -> rem(x, 2) == 0 end)
|> Enum.sum()
|> dbg()
$ elixir sample_pipe.exs
[sample_pipe.exs:5: (file)]
1..10 #=> 1..10
|> Enum.map(fn x -> x * x end) #=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
|> Enum.filter(fn x -> rem(x, 2) == 0 end) #=> [4, 16, 36, 64, 100]
|> Enum.sum() #=> 220

IExで実行するとステップ実行になる!

上記のpipeと組み合わせて使うだけでも十分便利なのですが、dbg/2 をIExで利用すると行単位でのステップ実行が可能になります!

試しに実行してみます。Request to pry との表示が出て、その後 Y で許可すると iex(1)> の部分が pry(1)> となっています。

$ iex sample_pipe.exs
Erlang/OTP 24 [erts-12.0.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Request to pry #PID<0.108.0> at sample_pipe.exs:1

    1: 1..10
    2: |> Enum.map(fn x -> x * x end)
    3: |> Enum.filter(fn x -> rem(x, 2) == 0 end)
    4: |> Enum.sum()

Allow? [Yn] Y
Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)>

pipe1つ1つがbreakpointとなっており next または n を入力すると処理を進めることができます。

pryの挙動を詳しく知りたい場合はIExのドキュメントを見ると良いでしょう。

https://hexdocs.pm/iex/IEx.Helpers.html#next/0
pry(1)> n
1..10 #=> 1..10

Break reached: sample_pipe.exs:2

    1: 1..10
    2: |> Enum.map(fn x -> x * x end)
    3: |> Enum.filter(fn x -> rem(x, 2) == 0 end)
    4: |> Enum.sum()
    5: |> dbg()

pry(1)> n
|> Enum.map(fn x -> x * x end) #=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Break reached: sample_pipe.exs:3

    1: 1..10
    2: |> Enum.map(fn x -> x * x end)
    3: |> Enum.filter(fn x -> rem(x, 2) == 0 end)
    4: |> Enum.sum()
    5: |> dbg()

pry(1)> n
|> Enum.filter(fn x -> rem(x, 2) == 0 end) #=> [4, 16, 36, 64, 100]

Break reached: sample_pipe.exs:4

    1: 1..10
    2: |> Enum.map(fn x -> x * x end)
    3: |> Enum.filter(fn x -> rem(x, 2) == 0 end)
    4: |> Enum.sum()
    5: |> dbg()

pry(1)> n
|> Enum.sum() #=> 220

--no-pry を指定すればこの挙動を無効にして、即座に dbg/2 の結果を確認できます。

$ iex --no-pry sample_pipe.exs

Erlang/OTP 24 [erts-12.0.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

[sample_pipe.exs:5: (file)]
1..10 #=> 1..10
|> Enum.map(fn x -> x * x end) #=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
|> Enum.filter(fn x -> rem(x, 2) == 0 end) #=> [4, 16, 36, 64, 100]
|> Enum.sum() #=> 220

Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
iex>

この規模感だとありがたみがあまりないかもしれませんが、pipeの中の処理をさらにデバッグしたい場合に強力だと思います。便利です!

1..10
|> Enum.map(fn x -> x * x end)
|> Enum.filter(fn x ->
  dbg(x) # ここもbreakpointになる

  rem(x, 2) == 0
end)
|> Enum.sum()
|> dbg()

dbgをLivebookで利用する

さらにはIExだけでなく、Livebook上で dbg/2 を利用すると、pipeの結果を見つつ並べ替えたり、pipeの動作をon/offできたりといったことが可能になっています。

ぜひ前述の公式ブログ内の動画を見てみてください。以下引用してリンク貼っておきます。

Livebookでdebugを利用する動画

まとめ

v1.14で導入された dbg/2 について紹介しました。これまでは静的に値を見たい場合は IO.inspect を使い、動的に値を見たい場合は IEx.pry を使って、、と使い分けていましたが、今後は dbg/2 のみでサクサクとデバッグが行えそうです。是非お試しください!

Discussion

ログインするとコメントできます