はじめに
Enum.mapの処理手順をすべてiexで動かしながら、いかに手軽に確認できるかを考えました。
前回、
(初学者向け)すべてiexで簡単に、Enum.mapの動きをデバッグで見たい(IO.inspect 編)で書いたのですが、納得いくものではなったです。そこで、Elixirの偉大な先輩方にElixir1.14からdbg
あるよと教えていただき、改めて作り直しました。
余談(ポエム)
dbg
には、さまざま機能がありますが、具体的に何が出来るのか、身体で覚えていきたいと思います。
身体で覚えるとは、自ら手を動かしてハマって試し、「わかる」から「できる」になるという意味合いです。
身体で覚えるElixirシリーズ書いて行こうかな。確実に女性には嫌われるけど。lol
やりたいのは、「すべてiexで簡単に、Enum.mapの動きをデバッグで見たい」という事です。
普通に関数で書けば実現できるのは前回書きましたが、iex
で楽してやりきりたい。そうしたら、今後気楽にiex
でデバッグなんかもしながら、何でも分かった気になれるのではないかという淡い期待を元に書いています。
余談に付き合って頂きありがとうございます。
dbgとは、から書いていきます。
dbg/2 とは
Elixir v1.14から、デバッグの強化のためにdbg/2
マクロが提供されました。
dbg/2
は、BEAM VM上でイベントをトレースする機能を提供します。関数の呼び出しと戻り、例外の発生、メッセージの送受信、スポーン、終了、リンク、スケジューリング、ガベージコレクションなどの多くのイベント(ノードの集まり)の端から端までトレースすることができます。
IExの有無にかかわらず使用できるマクロです。
dbg/2
はKernelモジュールに属し、どこからでも呼び出せます。
引数なしのdbg()
で、現在束縛している変数の情報を表示します。
今回は、dbg()
しか使わないので、詳細を知りたい方は公式を参考にしてください。
hexdocs dbg はコチラ
※Elixir v1.14からしか利用できないので、ご注意ください。
公式のElixir v1.14 releasedはこちら
dbg()
の基本的な使い方
例えば、以下のような関数を書きます。
dbg()
は、実行されるタイミングで、その時点で束縛している変数の中身を出力してくれます。
また、dbg()
を入れた関数を実行すると、リプライがpry>
に変わり、n
を入力する毎に、次のdbg()
を実行してくれます。
言葉でいうと話が長く伝わりにくいので、実際に動かしてみてください。
実行結果は以下に貼り付けております。
$ iex
Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
iex> defmodule Ex3 do
iex> def add(a) do
iex> dbg()
iex> r = a + 1
iex> dbg()
iex> r = r * 3
iex> dbg()
iex> end
iex> end
{:module, Ex3,
<<70, 79, 82, 49, 0, 0, 18, 252, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 1, 3,
0, 0, 0, 30, 10, 69, 108, 105, 120, 105, 114, 46, 69, 120, 51, 8, 95, 95,
105, 110, 102, 111, 95, 95, 10, 97, 116, ...>>, {:add, 1}}
iex> Ex3.add(1)
Break reached: Ex3.add/1 (iex:3)
pry> n
binding() #=> [a: 1]
Break reached: Ex3.add/1 (iex:5)
pry> n
binding() #=> [a: 1, r: 2]
Break reached: Ex3.add/1 (iex:7)
pry> n
binding() #=> [a: 1, r: 6]
[a: 1, r: 6]
Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
こんな感じでIO.inspect
と比べるとシンプルにデバッグできます。
でも、これって行単位の実行だからEnum.map
の中の動きはトレース出来なくない?
と今更ながら気付くのです。この辺りは、Elixirの偉大な先輩方は自分で考える領域を残してくれています。結局自分で考えないと、記憶にも記録にも残らないのでとてもありがたいです。
もう一度、確認します。やりたかった事は何か?
「すべてiexで簡単に、Enum.mapの動きをデバッグで見たい」という事です。
まず、前回のおさらいです。
デバッグしたいのはこの処理です。
iex(2)> Enum.map([1, 2, 3], &(&1 + 1))
[2, 3, 4]
前回までは、IO.inspect
を使って、このような形でインプット、アウトプットを出力し、処理状況が見えるようになりました。
iex(1)> Enum.map([1, 2, 3], &(IO.inspect(&1) + 1))
1
2
3
[2, 3, 4]
今回は、上記を改良していきます。
まず、何も考えないなら、IO.inspect
をdbg
に置き換えます。
iex(1)> Enum.map([1, 2, 3], &(dbg(&1) + 1))
Break reached: iex:1
pry(1)> n
x1 #=> 1
Break reached: iex:1
pry(1)> n
x1 #=> 2
Break reached: iex:1
pry(1)> n
x1 #=> 3
[2, 3, 4]
Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
dbg
の出力結果を見ると、&1
がx1
として、中身が表示されました。
いい。これでいいんだけど、前にdbg
入れたらどうなるのかな?
iex(3)> Enum.map([1, 2, 3], &(dbg(&1 + 1))
...(3)> )
Break reached: iex:3
pry(1)> n
x1 + 1 #=> 2
Break reached: iex:3
pry(1)> n
x1 + 1 #=> 3
Break reached: iex:3
pry(1)> n
x1 + 1 #=> 4
[2, 3, 4]
Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
計算過程x1 + 1 #=> 2
が見えます。
でも、x1 #=> 1
は消えました。
パイプで繋いだら見え方変わるかな?
ということで、やってみます。
iex(1)> Enum.map([1, 2, 3], &(&1 + 1 |> dbg()))
Break reached: iex:1
pry(1)> n
x1 + 1 #=> 2
Break reached: iex:1
pry(1)> n
x1 + 1 #=> 3
Break reached: iex:1
pry(1)> n
x1 + 1 #=> 4
[2, 3, 4]
Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
結果は同じです。
という事は、これまでの合わせ技で以下のようにdbg
を2つ書けばいいのではと考えました。
iex(1)> Enum.map([1, 2, 3], &dbg(dbg(&1) + 1))
Break reached: iex:1
pry(1)> n
Break reached: iex:1
pry(1)> n
x1 #=> 1
dbg(x1) + 1 #=> 2
Break reached: iex:1
pry(1)> n
Break reached: iex:1
pry(1)> n
x1 #=> 2
dbg(x1) + 1 #=> 3
Break reached: iex:1
pry(1)> n
Break reached: iex:1
pry(1)> n
x1 #=> 3
dbg(x1) + 1 #=> 4
[2, 3, 4]
Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
うん。理想的な結果だと思います。
しかし、課題は2つ残りました。
-
dbg
を2回も書かないといけないという余分なコーディング - リプライがpry(1)>に変わった後、
n
を2回実行しないと一つ目の出力結果が表示されないという余分な操作。
2つ目については、偉大な先輩からアドバイスを頂き、--no-pry
あるよと教えて頂きました。
必要な変数の束縛内容は出力されているのだから、止めてpry(のぞき見)する必要もないと気が付き、以下の通りiex --no-pry
でスッキリ出力しました。
$ iex --no-pry
Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
iex> Enum.map([1, 2, 3], &dbg(dbg(&1) + 1))
[iex:1: (file)]
x1 #=> 1
[iex:1: (file)]
dbg(x1) + 1 #=> 2
[iex:1: (file)]
x1 #=> 2
[iex:1: (file)]
dbg(x1) + 1 #=> 3
[iex:1: (file)]
x1 #=> 3
[iex:1: (file)]
dbg(x1) + 1 #=> 4
[2, 3, 4]
確かに、課題は残りましたが十分だと思います。
また、解決策が分かれば追記します!
もっといい方法があればご教授いただければ幸いです。