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文字するめる→文字列に変換 を実施する処理なっています。
"ぃだのせをせをのぃだ"
|> 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モードではn
、continue
、respawn
のいずれかを入力することで、処理を次に進めます。いわゆるステップ実行ですね。
それぞれ、以下の操作となっています。
-
n
: 次のパイプへ処理をジャンプしたいときに入力します。 -
continue
: 次以降の処理を一気に進めるが、pryモード状態は維持したい場合に入力します。 -
respawn
: 次以降の処理を一気に進めて、pryモードからも抜ける場合に入力します。
ここでは、n
とcontinue
のケースを紹介します。
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
」です。
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)>
改めて、list
と new_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()
を拡張した動作もできるようです。
こちらは機会があれば試してみようと思います。