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

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()を試す（その１）：各行の実行結果のデバッグ出力

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

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

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

```elixir:./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モードでは`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    ...（※５:再び「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`」です。

```elixir:./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)>
```

改めて、`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()` を拡張した動作もできるようです。
こちらは機会があれば試してみようと思います。