-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add more info for :with expressions in dbg/1 #13891
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -652,6 +652,162 @@ defmodule MacroTest do | |
| """ | ||
| end | ||
|
|
||
| test "with with/1 (all clauses match)" do | ||
| opts = %{width: 10, height: 15} | ||
|
|
||
| {result, formatted} = | ||
| dbg_format( | ||
| with {:ok, width} <- Map.fetch(opts, :width), | ||
| double_width = width * 2, | ||
| IO.puts("just a side effect"), | ||
| {:ok, height} <- Map.fetch(opts, :height) do | ||
| {:ok, double_width * height} | ||
| end | ||
| ) | ||
|
|
||
| assert result == {:ok, 300} | ||
|
|
||
| assert formatted =~ "macro_test.exs" | ||
|
|
||
| assert formatted =~ """ | ||
| With clauses: | ||
| Map.fetch(opts, :width) #=> {:ok, 10} | ||
| width * 2 #=> 20 | ||
| Map.fetch(opts, :height) #=> {:ok, 15} | ||
|
|
||
| With expression: | ||
| with {:ok, width} <- Map.fetch(opts, :width), | ||
| double_width = width * 2, | ||
| IO.puts("just a side effect"), | ||
| {:ok, height} <- Map.fetch(opts, :height) do | ||
| {:ok, double_width * height} | ||
| end #=> {:ok, 300} | ||
| """ | ||
| end | ||
|
|
||
| test "with with/1 (no else)" do | ||
| opts = %{width: 10} | ||
|
|
||
| {result, formatted} = | ||
| dbg_format( | ||
| with {:ok, width} <- Map.fetch(opts, :width), | ||
| {:ok, height} <- Map.fetch(opts, :height) do | ||
| {:ok, width * height} | ||
| end | ||
| ) | ||
|
|
||
| assert result == :error | ||
|
|
||
| assert formatted =~ "macro_test.exs" | ||
|
|
||
| assert formatted =~ """ | ||
| With clauses: | ||
| Map.fetch(opts, :width) #=> {:ok, 10} | ||
| Map.fetch(opts, :height) #=> :error | ||
|
|
||
| With expression: | ||
| with {:ok, width} <- Map.fetch(opts, :width), | ||
| {:ok, height} <- Map.fetch(opts, :height) do | ||
| {:ok, width * height} | ||
| end #=> :error | ||
| """ | ||
| end | ||
|
|
||
| test "with with/1 (else clause)" do | ||
| opts = %{width: 10} | ||
|
|
||
| {result, formatted} = | ||
| dbg_format( | ||
| with {:ok, width} <- Map.fetch(opts, :width), | ||
| {:ok, height} <- Map.fetch(opts, :height) do | ||
| width * height | ||
| else | ||
| :error -> 0 | ||
| end | ||
| ) | ||
|
|
||
| assert result == 0 | ||
| assert formatted =~ "macro_test.exs" | ||
|
|
||
| assert formatted =~ """ | ||
| With clauses: | ||
| Map.fetch(opts, :width) #=> {:ok, 10} | ||
| Map.fetch(opts, :height) #=> :error | ||
|
|
||
| With expression: | ||
| with {:ok, width} <- Map.fetch(opts, :width), | ||
| {:ok, height} <- Map.fetch(opts, :height) do | ||
| width * height | ||
| else | ||
| :error -> 0 | ||
| end #=> 0 | ||
| """ | ||
| end | ||
|
|
||
| test "with with/1 (guard)" do | ||
| opts = %{width: 10, height: 0.0} | ||
|
|
||
| {result, formatted} = | ||
| dbg_format( | ||
| with {:ok, width} when is_integer(width) <- Map.fetch(opts, :width), | ||
| {:ok, height} when is_integer(height) <- Map.fetch(opts, :height) do | ||
| width * height | ||
| else | ||
| _ -> nil | ||
| end | ||
| ) | ||
|
|
||
| assert result == nil | ||
| assert formatted =~ "macro_test.exs" | ||
|
|
||
| assert formatted =~ """ | ||
| With clauses: | ||
| Map.fetch(opts, :width) #=> {:ok, 10} | ||
| Map.fetch(opts, :height) #=> {:ok, 0.0} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good example that shows that we should add a bit more info why this failed - it looks ok at the beginning but it's the problematic line that did fail and you have to jump back and forth to compare. match?({:ok, height} when is_integer(height), {:ok, 0.0}) #=> false
or
{:ok, height} when is_integer(height) <- {:ok, 0.0}There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another example is pattern match on map key but the map is not including the key. This is one of the hardest to spot for me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about showing it this way? {:ok, width} <- {:ok, 10}
{:ok, height} <- :errorThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a trade-off, cf discussion above.
This approach is also consistent with what we do for
So, I think it's a reasonable trade-off, even if I understand for this small example that it might look nice. Another possibility to address the "clutter" concern could be to inspect the pattern algebra with a However, if I'm overthinking the verbosity concern and everybody is convinced of the benefit, I could easily change the code to replace with the whole arrow node. |
||
|
|
||
| With expression: | ||
| with {:ok, width} when is_integer(width) <- Map.fetch(opts, :width), | ||
| {:ok, height} when is_integer(height) <- Map.fetch(opts, :height) do | ||
| width * height | ||
| else | ||
| _ -> nil | ||
| end #=> nil | ||
| """ | ||
| end | ||
|
|
||
| test "with with/1 (guard in else)" do | ||
| opts = %{} | ||
|
|
||
| {result, _formatted} = | ||
| dbg_format( | ||
| with {:ok, width} <- Map.fetch(opts, :width) do | ||
| width | ||
| else | ||
| other when is_integer(other) -> :int | ||
| other when is_atom(other) -> :atom | ||
| end | ||
| ) | ||
|
|
||
| assert result == :atom | ||
| end | ||
|
|
||
| test "with with/1 respects the WithClauseError" do | ||
| value = Enum.random([:unexpected]) | ||
|
|
||
| error = | ||
| assert_raise WithClauseError, fn -> | ||
| dbg( | ||
| with :ok <- value do | ||
| true | ||
| else | ||
| :error -> false | ||
| end | ||
| ) | ||
| end | ||
|
|
||
| assert error.term == :unexpected | ||
| end | ||
|
|
||
| test "with \"syntax_colors: []\" it doesn't print any color sequences" do | ||
| {_result, formatted} = dbg_format("hello") | ||
| refute formatted =~ "\e[" | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we include the format we were expecting as well? So the whole
{:ok, width} <- Map.fetch...? And then the outcome in the next line:Although I also worry that this will be too verbose for actual long values. Imagine long clauses with each of them returning a struct as a value (or an ecto schema).
So I do wonder if the best way to go is indeed to say which clause failed, allowing the user to further debug themselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thinking here is that we print the whole
withjust below, so just showing the expressions felt enough to debug the pipeline? As you said, I was fearing it could be too verbose otherwise.I agree this might be a reasonable approach if really there is an issue with showing all steps, but given this is what we do for pipelines I don't think it should be an issue?
Most of the complexity comes from handling guards & raise, showing all steps is not the hard part I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, sounds good.
But can we argue any other behaviour for pipelines? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually... :) One could argue that one might just add
|> dbg()after every step they care about, and we just show the last one, in order to avoid noise. Allowing the user to further debug themselves.I very rarely felt this way, but it did happen to me very anecdotally to fallback to
|> IO.inspectafter a pipeline for this reason.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. And it is actually easier to dbg individual parts in a pipeline then in a
with.I guess another way to explore this is: if we had an actual debugger, would we like to go clause by clause and else by else? The answer would be yes, so we should probably follow that principle. So this looks good to me.