LoginSignup
7
2

More than 1 year has passed since last update.

Elixir1.14で追加されたdbgがいい感じです

Last updated at Posted at 2022-08-10

Elixir 1.14.0-rc.0から dbg() と呼ばれるデバッグ用のマクロが追加されました。
dbg()はKernelモジュールに属し、どこからでも呼び出せます。

dbg()についての公式ドキュメントはこちら。
https://hexdocs.pm/elixir/1.14.0-rc.0/Kernel.html#dbg/2
https://hexdocs.pm/iex/1.14.0-rc.0/IEx.html#module-dbg-and-breakpoints

動作確認してみたところ「いい感じ」と思えたので、簡単に記録しておきます。

事前準備:1.14.0-rc.0のインストール

最初にも書いていますが、dbg()はElixirの1.14.0-rc.0以降から利用できる機能です。

asdfなどを利用して、1.14.0-rc.0以降のElixirをインストールしておいてください。
以下、asdfでインストールする場合の例です。

この例ではOTP25でインストールしています。事前にErlangのバージョン25もインストールしておいてください。

asdf install elixir 1.14.0-rc.0-otp-25

インストールが終わったら、実行できるように、globalまたはlocalで、バージョンの指定をします。

asdf local elixir 1.14.0-rc.0-otp-25

iexを起動すると、Elixirのバージョンが1.14.0-rc.0であることが確認できました。

$iex
Erlang/OTP 25 [erts-13.0.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

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

dbg()を試す(その1):各行の実行結果のデバッグ出力

Elixirのexファイルの処理中にdbg()を含める(正確には、dbg/2に対して「コード」を引数として渡す)ことで、その処理の各結果を確認できます。

具体例を以下に示します。

以下の処理が書かれたexファイルを作成します。
こちらは、文字列→文字リスト→文字リストの文字を1文字するめる→文字列に変換 を実施する処理なっています。

./dbg_sample/ichihazen.ex
"ぃだのせをせをのぃだ"
|> to_charlist
|> Enum.map(fn s -> s + 1 end)
|> to_string
|> dbg()

このファイルをelixirコマンドで実行します。
すると、IO.inspect/2と同様の処理結果を各行で出力してくれます。

$elixir ./dbg_sample/ichihazen.ex
[dbg_sample/ichihazen.ex:5: (file)]
"ぃだのせをせをのぃだ" #=> "ぃだのせをせをのぃだ"
|> to_charlist #=> [12355, 12384, 12398, 12379, 12434, 12379, 12434, 12398, 12355, 12384]
|> Enum.map(fn s -> s + 1 end) #=> [12356, 12385, 12399, 12380, 12435, 12380, 12435, 12399, 12356, 12385]
|> to_string #=> "いちはぜんぜんはいち"

dbg()を試す(その2):iex上でpryモード起動

dbg()は、iex上からも動作できます。

iexを起動後、dbg()を含んだをexファイルをcコマンドでコンパイルし、実行するとdbg()が動作します。

たとえば、前述の./dbg_sample/ichihazen.exをiex上でコンパイルすると、pryを実行するかを聞いてきます。

Allow? [Yn]に対して、nで答えると、elixirコマンドで実行した時のように、各行の実行結果をデバッグ出力してくれています。

$iex
Erlang/OTP 25 [erts-13.0.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.14.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c "./dbg_sample/ichihazen.ex"   ...(※1:ここで「exファイル」をコンパイルする。)
Request to pry #PID<0.109.0> at dbg_sample/ichihazen.ex:5

    2: |> to_charlist
    3: |> Enum.map(fn s -> s + 1 end)
    4: |> to_string
    5: |> dbg()

Allow? [Yn] n    ...(※2:ここで「n」を入力してリターンする。)
"ぃだのせをせをのぃだ" #=> "ぃだのせをせをのぃだ"

|> to_charlist #=> [12355, 12384, 12398, 12379, 12434, 12379, 12434, 12398, 12355, 12384]

|> Enum.map(fn s -> s + 1 end) #=> [12356, 12385, 12399, 12380, 12435, 12380, 12435, 12399, 12356, 12385]

|> to_string #=> "いちはぜんぜんはいち"

[]

Allow? [Yn]に対して、Yで答えると、pryモードに切り替わります。
pryモードではncontinuerespawnのいずれかを入力することで、処理を次に進めます。いわゆるステップ実行ですね。

それぞれ、以下の操作となっています。

  • n: 次のパイプへ処理をジャンプしたいときに入力します。
  • continue: 次以降の処理を一気に進めるが、pryモード状態は維持したい場合に入力します。
  • respawn: 次以降の処理を一気に進めて、pryモードからも抜ける場合に入力します。

ここでは、ncontinueのケースを紹介します。

nを入力した場合:

なお、nで処理を進めていく場合、nの処理対象になっている関数は太字になっています。

iex(2)> c "./dbg_sample/ichihazen.ex"    ...(※1:ここで「exファイル」をコンパイルする。)
Request to pry #PID<0.114.0> at dbg_sample/ichihazen.ex:5

    2: |> to_charlist
    3: |> Enum.map(fn s -> s + 1 end)
    4: |> to_string
    5: |> dbg()

Allow? [Yn] Y    ...(※2:ここで「Y」を入力してリターンする。)

Interactive Elixir (1.14.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)> n    ...(※3:ここで「n」を入力してリターンすると現状のデータのデバッグ出力をして、次のパイプの処理に進む)
"ぃだのせをせをのぃだ" #=> "ぃだのせをせをのぃだ"

Break reached: ichihazen.ex:2

    1: "ぃだのせをせをのぃだ"
    2: |> to_charlist
    3: |> Enum.map(fn s -> s + 1 end)
    4: |> to_string
    5: |> dbg()

pry(1)> n    ...(※4:再び「n」を入力してリターンすると、現状のデータのデバッグ出力をして、次のパイプの処理に進む)
|> to_charlist #=> [12355, 12384, 12398, 12379, 12434, 12379, 12434, 12398, 12355, 12384]

Break reached: ichihazen.ex:3

    1: "ぃだのせをせをのぃだ"
    2: |> to_charlist
    3: |> Enum.map(fn s -> s + 1 end)
    4: |> to_string
    5: |> dbg()

pry(1)> n    ...(※5:再び「n」を入力してリターンすると、現状のデータのデバッグ出力をして、次のパイプの処理に進む)
|> Enum.map(fn s -> s + 1 end) #=> [12356, 12385, 12399, 12380, 12435, 12380, 12435, 12399, 12356, 12385]

Break reached: ichihazen.ex:4

    1: "ぃだのせをせをのぃだ"
    2: |> to_charlist
    3: |> Enum.map(fn s -> s + 1 end)
    4: |> to_string
    5: |> dbg()

pry(1)> n    ...(※6:再び「n」を入力してリターンすると、現状のデータのデバッグ出力をして、次のパイプの処理に進む。)
|> to_string #=> "いちはぜんぜんはいち"

** (EXIT from #PID<0.249.0>) shell process exited with reason: shutdown
[]

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

次のパイプの処理がない場合、そこでpryモードは終了となる。

continueを入力した場合:

continueと入力すると、一気に処理を進めていきます。そして、最後まで処理を進めたのち、またpryモードに戻ります。
ただし、今回の./dbg_sample/ichihazen.exのコード内容だと、pryモードの操作をうまく表現できないです。
なので、continueの詳しい挙動は、次の段落で説明します。

iex(2)> c "./dbg_sample/ichihazen.ex"    ...(※1:ここで「exファイル」をコンパイルする。)
Request to pry #PID<0.114.0> at dbg_sample/ichihazen.ex:5

    2: |> to_charlist
    3: |> Enum.map(fn s -> s + 1 end)
    4: |> to_string
    5: |> dbg()

Allow? [Yn] Y    ...(※2:ここで「Y」を入力してリターンする。)

Interactive Elixir (1.14.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)> continue    ...(※3:ここで「continue」を入力してリターンすると次の処理に進む)
"ぃだのせをせをのぃだ" #=> "ぃだのせをせをのぃだ"

|> to_charlist #=> [12355, 12384, 12398, 12379, 12434, 12379, 12434, 12398, 12355, 12384]

|> Enum.map(fn s -> s + 1 end) #=> [12356, 12385, 12399, 12380, 12435, 12380, 12435, 12399, 12356, 12385]

|> to_string #=> "いちはぜんぜんはいち"

[]
** (EXIT from #PID<0.114.0>) shell process exited with reason: shutdown

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

iexを「--no-pry」モードで起動する

なお、iexの起動時にオプション--no-pryを付与して立ち上げると、dbg()は処理結果だけを出力してくれます。
※前述のAllow?nで答えた時と同じ。

$iex --no-pry
Erlang/OTP 25 [erts-13.0.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.14.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c "./dbg_sample/ichihazen.ex"
[dbg_sample/ichihazen.ex:5: (file)]
"ぃだのせをせをのぃだ" #=> "ぃだのせをせをのぃだ"
|> to_charlist #=> [12355, 12384, 12398, 12379, 12434, 12379, 12434, 12398, 12355, 12384]
|> Enum.map(fn s -> s + 1 end) #=> [12356, 12385, 12399, 12380, 12435, 12380, 12435, 12399, 12356, 12385]
|> to_string #=> "いちはぜんぜんはいち"

[]
iex(2)>

dbg()を試す(その3):iex上でpryモード起動しステップを一気に実行する

pryモードで起動した場合、値(というよりも関数)の確認とステップ実行が可能となります。
ここでは、ステップを一気に実行するcontinueの場合を紹介します。

たとえば、次のようなexファイルがあったとします。
関数take_one_step_forwardの中で、dbg()を2回呼んでいます。引数で渡された「list」と、変換処理された結果の「new_list」です。

./dbg_sample/ichihazen_2.ex
defmodule Zen do
  def answer do
    "ぃだのせをせをのぃだ"
    |> to_charlist
    |> take_one_step_forward
    |> to_string
  end

  def take_one_step_forward(list) do
    dbg(list)
    new_list = list |> Enum.map(fn s -> s + 1 end)
    dbg(new_list)

    new_list
  end
end

これを、iexの中でコンパイルします。

$iex
Erlang/OTP 25 [erts-13.0.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.14.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c "./dbg_sample/ichihazen_2.ex"   ...(※1:ここで「exファイル」をコンパイルする。)
[Zen]
iex(2)> Zen.answer   ...(※2:ここで「Zen.answer」を実行する。)
Break reached: Zen.take_one_step_forward/1 (dbg_sample/ichihazen_2.ex:11)

    8:   end
    9:
   10:   def take_one_step_forward(list) do
   11:     dbg(list)
   12:     new_list = list |> Enum.map(fn s -> s + 1 end)
   13:     dbg(new_list)
   14:

pry(1)>

この時点では、11行目のdbg(list)直前までは処理が実行されています。
そのため、listを指定すれば、listの中身(正確には式「list」を実行した結果)が分かります。

pry(1)> list
[12355, 12384, 12398, 12379, 12434, 12379, 12434, 12398, 12355, 12384]

一方、13行目の処理はまだなので、この時点でnew_listを指定すると、エラーが出力されます。

pry(2)> new_list
warning: variable "new_list" does not exist and is being expanded to "new_list()", please use parentheses to remove the ambiguity or change the variable name
  dbg_sample/ichihazen_2.ex:2: Zen.take_one_step_forward/1

** (UndefinedFunctionError) function :erl_eval.new_list/0 is undefined or private. Did you mean:

      * expr_list/2
      * expr_list/3
      * expr_list/4

    (stdlib 4.0.1) :erl_eval.new_list/0
    (stdlib 4.0.1) erl_eval.erl:660: :erl_eval.local_func/8
    /path/to/elixir/ex_1.14.X-otp-25/dbg_sample/ichihazen_2.ex:2: (file)

では、処理を進めるためにcontinueを入力し、リータンします。
そうすると、11行目のdbg(list)が実行され、その値がデバッグ出力されます。
そして、13行目のdbg(new_list)の直前まで処理が進んでいます。

pry(2)> continue
list #=> [12355, 12384, 12398, 12379, 12434, 12379, 12434, 12398, 12355, 12384]

Break reached: Zen.take_one_step_forward/1 (dbg_sample/ichihazen_2.ex:13)

   10:   def take_one_step_forward(list) do
   11:     dbg(list)
   12:     new_list = list |> Enum.map(fn s -> s + 1 end)
   13:     dbg(new_list)
   14:
   15:     new_list
   16:   end

pry(1)>

改めて、listnew_list をそれぞれ指定してみると...

pry(1)> list
[12355, 12384, 12398, 12379, 12434, 12379, 12434, 12398, 12355, 12384]
pry(2)> new_list
[12356, 12385, 12399, 12380, 12435, 12380, 12435, 12399, 12356, 12385]

どちらも、値を確認できました。

なお、これまでは個別に変数を指定していましたが、bindingと入力すると現時点でバインドされている全ての変数の値を確認できます。

pry(1)> binding
[
  list: [12355, 12384, 12398, 12379, 12434, 12379, 12434, 12398, 12355, 12384],
  new_list: [12356, 12385, 12399, 12380, 12435, 12380, 12435, 12399, 12356,
   12385]
]
pry(2)>

最後に、もう一回continueを実行すると、改めて13行目のdbg(new_list)が実行されて、そのdbg()がないため終わりまで処理が実行されました。

pry(3)> continue
new_list #=> [12356, 12385, 12399, 12380, 12435, 12380, 12435, 12399, 12356, 12385]

"いちはぜんぜんはいち"

まとめ

本記事では、Elixirのdbg()がいい感じということで紹介させていただきました。
マニュアルを見ると「Configuring the debug function(デバッグ関数の設定) の記述もあり、dbg() を拡張した動作もできるようです。
こちらは機会があれば試してみようと思います。

7
2
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
7
2