EDIT: Since Elixir 1.14, there is a new macro dbg/2
that substitute the Inspector package.
I have been tinkering with better ways to improve my Elixir workflow and during this journey I realized that I am, somehow, trying to bring things which I'm used with from other environments, the same way a foreign citizen does when living in a different country.
Elixir prizes highly the Developer Experience, so that we have the astounding tools to generate documentation, first quality package management, and an excellent and mature build tool, and a REPL, iex
that allows fast experiments, but I think that may I have pushed a lot the expected usage of it, in a not intended way as it was planned by the creators.
We can feel it during a IEx.pry/0
usage, it's a pretty limited tool, if you are accustomed to use Ruby pry for example, but given that Elixir doesn't hold mutable state, maybe it isn't necessary at all, so I started to think about the root cause of my dissatisfaction and I found it.
Debugging
Elixir is blaze fast, and we can notice it clearly when running a test suite with a lot of tests, they run really fast and the maintainers are working hard to make it even faster, so the so called Puts debugging is more than enough.
But what really hurts is to type always and everyplace the same think, IO.inspect/2
, passing ad-hoc variables. It's not uncommon to make several calls to IO.inspect/2
during a debugging session, so if we improve it, enhancing the IO.inspect/2
?
This is how Inspector was born, a library that makes debugging less annoying. You need only to place it in the calling site, like this:
# If this code is on lib/my_module.ex
defmodule MyModule do
def my_function(args) do
awesome_var = :foo
amazing_var = :bar
require Inspector; Inspector.i
end
end
MyModule.my_function(:hey)
And you will see in the stdout
:
inspector: %{
bindings: [
args: :hey,
awesome_var: :foo,
amazing_var: :bar
],
file: "lib/my_module.ex:5",
location: "MyModule.my_function/1"
}
If you uses Vim or Neovim, like me, you can even make it better with a key-map:
nnoremap <Leader>i orequire Inspector; Inspector.i<esc>
I hight recommend use it running this simple command:
Or with mix test.watch
found here
That is it folks. Happy debugging!
Top comments (5)
This is awesome! I wonder if this could be used to do something I've been vaguely thinking about for a long time. I very, very, very often do this to trace a specific variable:
I thought it would be nice if you could just do something like
inspect_v(variable)
and have it do that for me. I didn't realize thatbinding()
gives you a keyword list of the currently scoped variables, so maybe this is something that you could add toinspector
?Yes sure, I'll include it next week when I'll get some free time. Stay tunned
Always appreciate new projects and ideas! Am I right that IO.inspect(binding()) gives you almost the same without an extra dependency?
To me, knowing the location and file during the scanning of the inspect output on stdout/logs is a crucial feature, and in addition to how easy it's to just type
i()
. But people can still use theIO.inspect/2
and their variants without bother to bring another dependency (note, a development time dependency) at their own will.This approach is really unique and useful 🦄
Thanks for sharing, I learned something new