defmacro io_debug_inspect(t) do
t_name = Macro.to_string(t)
quote do
IO.puts "`#{unquote(t_name)}`: #{inspect(unquote(t), limit: :infinity, pretty: true)}"
end
end
defmacro io_debug_inspect(t) do
prefix = "`#{Macro.to_string(t)}`: "
quote do
IO.puts prefix <> "#{inspect(unquote(t), limit: :infinity, pretty: true)}"
end
end
That produces expanded output like this, which is fine, but not obviously better:
defmacro io_debug_inspect(t) do
t_name = Macro.to_string(t)
string = "\"`#{t_name}`: \#{inspect(#{t_name}, limit: :infinity, pretty: true)}\""
quote do
IO.puts unquote(string)
end
end
But, or so I’m guessing, string interpolation doesn’t work like above as the output is like this:
defmacro io_debug_inspect(t) do
t_name = Macro.to_string(t)
string = "\"`#{t_name}`: \#{inspect(#{t_name}, limit: :infinity, pretty: true)}\""
quote do
IO.puts unquote( Code.eval_string(string) )
end
end
Trying it:
iex> io_debug_inspect conn # TODO: Remove eventually.
warning: variable "conn" does not exist and is being expanded to "conn()", please use parentheses to remove the ambiguity or change the variable name
nofile:1
** (CompileError) nofile:1: undefined function conn/0
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(elixir) src/elixir_bitstring.erl:142: :elixir_bitstring.expand_expr/4
(elixir) src/elixir_bitstring.erl:27: :elixir_bitstring.expand/8
(elixir) src/elixir_bitstring.erl:20: :elixir_bitstring.expand/4
(elixir) lib/code.ex:232: Code.eval_string/3
...
I feel like there’s a way to do exactly what I originally wanted, but my original solution seems good enough.
But I’m curious as to how to match what I originally wanted, if it’s possible.
defmacro io_debug_inspect(t) do
prefix = "`#{Macro.to_string(t)}`: "
quote do
IO.puts prefix <> inspect(unquote(t), limit: :infinity, pretty: true)
end
end
Kernel.inspect does already return a string, no need to produce code that does an additional set of calls into String.Chars protocol.
It seems like it would be sensible to not try too hard to match some specific code when writing macros. Maybe just producing correct, and reasonable, code is the better target to aim for.
This reminds me of the suggestion to add a rust-like dbg macro into elixir. ^.^
Why not this though?
defmacro io_debug_inspect(t) do
label = "`#{Macro.to_string(t)}`"
quote do
IO.inspect(unquote(t), limit: :infinity, pretty: true, label: unquote(label))
end
end
I know that you’re looking for a macro as the solution for this, but an alternative to accomplish what appears to be your goal (and would work on any elixir project!) is to use an editor snippet. For example I have my editor setup so that when I type: lin<tab>conn it will add a line IO.inspect(conn, label: "conn") without me having to type conn twice. And of course it would be easy to add limit: :infinity, pretty: true to the snippet.
Effectively, they seem to be very similar. After testing with a little map, the only real difference is that yours returns the value of t; the output seems to be exactly the same.
I use Vim (really a GUI wrapper around Neovim), so I’m very much aware that I could use, e.g. a Vim macro, to generate this too.
But I share an Elixir project with someone that doesn’t use Vim! (I know – I’m still shocked by that discovery!) So, I figured a macro would be a little nicer for that person to use themselves; or any other future collaborators as well.
Obviously, macros are a bit of a trap – hence the general advice to avoid them where and when possible. Code generation is likewise strong evidence that either your programming language or tools are deficient in some significant way. But – sometimes – you need exactly that kind of booby-trapped implement to get something done.
This was all (mostly) an excuse to play around with Elixir macros. It is useful tho – I’ve been using it pretty regularly to debug integration tests for big, opaque blocks/trees of ‘legacy’ code.