Skip to content
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

Print methods(f) with color like stack traces #40251

Closed
wants to merge 31 commits into from

Conversation

mcabbott
Copy link
Contributor

@mcabbott mcabbott commented Mar 29, 2021

Should we re-use something like the new error printing for method lists?

Perhaps the module which(f) should be mostly omitted, to highlight only other-module methods?

Screenshot 2021-03-29 at 00 37 12

(Edit -- deleted an example. The one above does not yet have types in bold, added below.)

Closes #40913 Closes #30110 Closes #36129

@thchr
Copy link
Contributor

thchr commented Mar 29, 2021

This is no doubt subjective, but when I'm calling methods what I'm most interested in disambiguating is the different type signatures. This seems different from what one is interested in emphasizing for stack traces (e.g. different modules and path locations) and what is most clearly highlighted with this coloring/highlighting.

@mcabbott
Copy link
Contributor Author

mcabbott commented Mar 29, 2021

Agree the purpose is different. The hope would be that light grey paths could act as visual space between the methods. And that module colours would flag methods dealing with types they own -- like StaticArrays & BenchmarkTools here:

Screenshot 2021-03-29 at 09 13 03

Screenshot 2021-03-29 at 09 25 40

@JeffBezanson JeffBezanson added the display and printing Aesthetics and correctness of printed representations of objects. label Mar 29, 2021
@fingolfin
Copy link
Member

I really like this. However, I feel that the colored package names in your examples stand out most -- they are the first thing I see/read when looking at those images. I feel that's reverse from how it should be. Perhaps one can experiment a bit more with this. Either by deemphasizing the package names, or by emphasizing the method argument types more. Besides colors, one could also experiment with e.g. using bold face -- e.g. how does it look if the argument types are printed in bold?

@mcabbott
Copy link
Contributor Author

More subtle colours would be better, but hard to make things precise. Bold seems to vary from doing nothing to feeling like shouting, here's a very crude attempt:

Screenshot 2021-03-30 at 11 44 13

With more fiddling the colour could be moved to apply to the types owned by that package, not the module name. Or (simpler) to highlight the package name in the path, rather than alone.

@fingolfin
Copy link
Member

To me your last screenshot is by far the best in terms if being able to quickly pick out which methods are there and in particular finding any that I care about

@mcabbott
Copy link
Contributor Author

mcabbott commented Mar 30, 2021

A slightly more refined version, with only the outermost type bold. This avoids printing 4 lines of bold text at [12], perhaps other things like a::T, b::T (last line) are odd?

Screenshot 2021-03-30 at 15 17 42

Edit -- This also affects the printing of ambiguities, but not of no-method errors:

Screenshot 2021-03-30 at 22 03 04

@StefanKarpinski
Copy link
Member

Really like these! Seems ok if the coloring and emphasis of stack traces and methods lists are a bit different, but ideal to share the common printing logic aside from those details.

@fingolfin
Copy link
Member

Nice!

Any idea why in the ambiguity error the first Any is not bold?

@thchr
Copy link
Contributor

thchr commented Mar 31, 2021

That is nice!
I do still think, subjectively, that the module names are the most visibly distinct feature in the printout, but it's a lot easier to disambiguate now (and definitely a ton clearer than the current styling).

I wonder if the module name coloring is helpful at all in this context though. To me, right now, they remain the most visible feature in the list, drawing my eye to them as a kind of semi-noisy "headers" of sorts (and notably, the headers come after the content - this is confuses me in the stack trace printing as well a bit; I'm always finding a "header", then I need to remind myself to look upwards to see its associated content).

If the modules were all colored gray, the "where-is-this-method-actually-from" line would serve as a nice and consistent "quasi-whitespace" of sorts between method signatures.
Just wondering out loud here, obviously - the proposed styling is already very nice..!

@mcabbott
Copy link
Contributor Author

Any idea why in the ambiguity error the first Any is not bold?

Probably a bad decision! In looking at examples with a few types & many ::Anys, I was trying to reduce their importance.

I wonder if the module name coloring is helpful at all in this context though.

The hope is that it's a proxy for the package for whose types a method is being added. How often things will end up monochrome or looking like a rainbow in actual use, will depend... I guess colours will be most useful when there are just a few "foreign" methods, which are either the ones you care about, or the ones you can ignore.

and notably, the headers come after the content - this is confuses me in the stack trace printing as well a bit

Agreed. There were other proposals in the giant stack trace threads, like highlighting the [10] or the function name, but (IIRC) these felt like visual puzzles to see what the colours went with. Qualifying the function names didn't look great, and moved further from how @warn etc. print locations.

Here, I worry that highlighting Type{GitCredentialHelper} would similarly introduce an annoying puzzle to solve. Whereas everyone is used to reading the stack traces (or soon will be!)

@oscardssmith oscardssmith changed the title Print methods(f) much like stack traces Print methods(f) with color like stack traces May 22, 2021
@oscardssmith oscardssmith added the REPL Julia's REPL (Read Eval Print Loop) label May 22, 2021
@mcabbott mcabbott marked this pull request as ready for review May 23, 2021 02:44
@JeffBezanson
Copy link
Member

ideal to share the common printing logic aside from those details.

This!

@mcabbott
Copy link
Contributor Author

mcabbott commented May 24, 2021

It would also be nice to unify the code which prints "MethodError: no method matching" with everything else --this PR doesn't change that, but affects "MethodError: ... ambiguous" because that is shared with methods(f), although not the "Possible fix" line. But (IMO) further unification should be another PR.

For this one, I guess we need to decide whether to do this at all. And whether the style here is what's wanted?

  • Does the module colouring make sense? The module which owns the function gets no colour (excepts its sub-modules are blue), while "foreign" modules extending it are given colours like the stack traces (but starting cool instead of warm).

  • Do bold types look OK on most people's screens?

  • "MethodError: ... ambiguous" uses an option to print the module & line number on the same line as the method. This is not used in stack traces, should it be used in methods(f)?

  • The length of the path & filename shown should be the same as for stack traces. This is quite long for e.g. LinearAlgebra: the error from det([1 2 3; 4 5 6]) wraps at 132 characters for me. Perhaps we should revisit that.

I fixed some doctests but there are some more... I'd rather not do that too many times.

@simeonschaub
Copy link
Member

This already looks great, sorry it got forgotten a bit! This seems preferable to #40916 since it is more consistent with stacktrace printing.

* Does the module colouring make sense? The module which owns the function gets no colour (excepts its sub-modules are blue), while "foreign" modules extending it are given colours like the stack traces (but starting cool instead of warm).

Oh, that's smart! Sounds reasonable to me.

* Do bold types look OK on most people's screens?

Looks good for me, I wouldn't expect this to be a big issue.

* "MethodError: ... ambiguous" uses an option to print the module & line number on the same line as the method. This is not used in stack traces, should it be used in `methods(f)`?

Maybe? Although I don't think the more compact printing is bad here either.

* The length of the path & filename shown should be the same as for stack traces. This is quite long for e.g. LinearAlgebra: the error from `det([1 2 3; 4 5 6])` wraps at 132 characters for me. Perhaps we should revisit that.

Do you have a screenshot of how this looks like for you? This probably isn't a dealbreaker though.

I fixed some doctests but there are some more... I'd rather not do that too many times.

Do you know about #40546 (comment)? You can even pass revise=true to do this without having to rebuild the whole sysimage.

@mcabbott
Copy link
Contributor Author

Do you know about #40546 (comment)?

Now I do, thanks! Will try that out.

Do you have a screenshot

Yes. This is the error printing with the pre-built Julia 1.6 -- some a long paths, and not useful ones. But perhaps orthogonal to this PR, which just re-uses the same path logic as for stack traces.

Screenshot 2021-05-25 at 10 14 14

@simeonschaub simeonschaub added the forget me not PRs that one wants to make sure aren't forgotten label May 26, 2021
@simeonschaub simeonschaub added this to the 1.7 milestone May 26, 2021
...
[180] +(a, b, c, xs...) in Base at operators.jl:424
[207] +(a::Any, b::Any, c::Any, xs::Any...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this change intentional? I think always printing the ::Any is a bit verbose and I am not sure it adds much information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's a bit verbose in cases like that. What it was meant to do was to help cases like this, where otherwise untyped arguments go missing:

Screenshot 2021-05-26 at 09 43 01

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be fine to omit the ::Any here, but I don't have any strong opinions on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some variants which print the module & path without a new line. (Because the new one above takes up a lot of lines compared to the old. And the types printed here are usually shorter than those in the stack traces.) I think this might be better.

On the left I omit the ::Any, what I don't like is that suddenly the variable name has to stand on its own, instead of being clearly secondary information to the signature. You can't answer "how many arguments?" by scanning "how many bold things?" anymore.

On the right, I've also added bold to required keywords, shown with cumsum. Seems like useful information to mark them somehow, hope bold isn't confusing --- all the types are also requirements, after all.

Screenshot 2021-05-26 at 12 01 35

@tecosaur
Copy link
Contributor

I made #40916 before someone pointed this out to me. I have no idea what's going to happen, but should this be the approach that ends up being used, I'd like to add some thoughts:

  1. I'm not a fan of the two line version. If I'm looking at something with ~200 methods (like +) I'd rather have 200 lines to look at than 400. I think an "intelligent" switch could make sense though (if num methods < val, etc.)
  2. I think argument names deserve more emphasis, in my PR I use the magenta colour for them. for example there's the join method with signature join(strings, delim, last) and I think it's worth applying emphasising argument names for cases such as those. If nothing else, since names are usually much shorter than type signatures this also makes it easier to count arguments, and resolves the ::Any problem.
  3. I really like how modules are given per-module colouring
  4. This may be outside the scope of this PR, but I think it's worth considering applying terminal hyperlinks to file names printed (in the terminal) by Julia

@mcabbott
Copy link
Contributor Author

mcabbott commented May 27, 2021

  1. I think I now agree that one line is better, here. The types are typically much shorter than in stack traces. And moving the coloured module names to the right doesn't seem weird, especially since only a subset of them are coloured.

  2. The signature which identifies the method is these types, while the variable names are just some details of the source code. It's good to show both but I do think the emphasis should be on the types. If you are looking at methods(f), I think it's often to see precisely what will match (or why it won't, or whether it will create ambiguity) rather than for general help.

  3. That said, there's room for more exploration of syntax highlighting in general. I think OhMyREPL only touches things you type; but perhaps there should be hooks to make it easier to also change how errors (and the printing of Expr) etc. are printed?

  4. Something like this hyperlinking would be great, here and in stack traces. Especially if it meant you could not print the whole path, which is adding a lot of clutter to all these examples. But I think this should be an independent PR.

Comment on lines +220 to +225
for kw in kwargs
skw = sym_to_string(kw)
if QuoteNode(kw) in m.roots # then it's required
printstyled(io, skw, color=:bold)
else
print(io, skw)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is meant to check which keyword arguments are required, and print those in bold.

Will this be robust? I don't know any details of the method struct, but in cases I could think of this appears to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Picture of this, BTW. Left is master, right this PR.

Screenshot 2021-05-27 at 00 20 00

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This question of whether the check for required keyword arguments is correct, safe, could use a look from someone who knows about things.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, this is not robust. but quite amusing that you found this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I just poke around... Is there a robust way to query this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know of one right now. Let's leave that for later work?

@tecosaur
Copy link
Contributor

  1. Cool
  2. I'm not sure my thinking lights up with yours. If we were in black & white you could only emphasise one thing, but by using a colour you can brightly label multiple, and the eye can more easily jump to what you're looking for. See my PR for an example.
  3. So do I, personally think it would be great if something like what I mock up in Add colour to show(::Method) #40916 (comment) could happen, and OhMyREPL was included in Julia
  4. I think this would make sense as an independent PR, yes. Affects this though, so I'll mention it here. If someone is willing to help me out (answer questions like which file should I put it in, how do I find X, ...) I'd like to do this as a learning experience for this codebase, since I like the idea of contributing to Julia 🙂 in particular I love how Julia has embraced Unicode, but I think it could benefit from going a bit further embracing colour and other features (like this)

@mcabbott
Copy link
Contributor Author

mcabbott commented May 27, 2021

Yes, it's true we aren't constrained to greyscale. I'm hesitant to touch the error printing, though... would it be weird if they diverge, is the information more or less useful in one than the other?

Here's what variables in :magenta looks like; this colour is not used for modules in methods(f) at the moment:

Screenshot 2021-05-27 at 10 54 57

I quite like this, but perhaps it's too fancy? (This isn't committed yet.)

Screenshot 2021-05-27 at 13 46 43

I think the code which is printing the path is in errorshow.jl, this moves it into a function print_module_path_file but leaves it there. The path gets altered by fixup_stdlib_path in methodshow.jl. It's all pretty messy, but with using Revise; Revise.track(Base) you can fairly easily mess with it and see what breaks.

@oscardssmith oscardssmith removed this from the 1.7 milestone May 27, 2021
@mcabbott
Copy link
Contributor Author

Bump, pre-1.8. Pinging @StefanKarpinski maybe?

Comment on lines +220 to +225
for kw in kwargs
skw = sym_to_string(kw)
if QuoteNode(kw) in m.roots # then it's required
printstyled(io, skw, color=:bold)
else
print(io, skw)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, this is not robust. but quite amusing that you found this

file, line = updated_methodloc(m)
print(io, " at ", file, ":", line)

# module & flie, re-using function from errorshow.jl
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# module & flie, re-using function from errorshow.jl
# module & file, re-using function from errorshow.jl

Comment on lines +292 to +294
modulecolordict = Dict{Module, Symbol}()
modulecolorcycler = Iterators.Stateful(Iterators.cycle(METHODLIST_MODULECOLORS))
modulecolordict[parentmodule_before_main(modul)] = :blue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really our only strategy for this? Global state through a particular list?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no memory of writing this :/ I presume I tried to copy the stack traces.

The list [:cyan, :green, :yellow] is global, but isn't modulecolorcycler local to this function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, true, seems to be a copy from there. I guess this is okay

@@ -96,7 +96,7 @@ Each statement gets analyzed for its total cost in a function called
as follows:
```jldoctest; filter=r"tuple.jl:\d+"
julia> Base.print_statement_costs(stdout, map, (typeof(sqrt), Tuple{Int},)) # map(sqrt, (2,))
map(f, t::Tuple{Any}) in Base at tuple.jl:179
map(f::Any, t::Tuple{Any}) @ Base tuple.jl:218
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a regression

Copy link
Contributor Author

@mcabbott mcabbott Jan 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what you mean?

I just landed here by chasing errors, and have not looked for the code which generates this. I believe that Base.Math.throw_complex_domainerror(:sqrt::Symbol, %2::Float64)::Union{} just below in this output does not take the path this PR changes, since it does not get printed in bold:

Screenshot 2022-01-19 at 18 09 22

Is there an alternative path that the first line's printing should take? Or should I add some option like fancy=false (or checking IOContext) to the basic show(method) path, to print this closer to the old style?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added ::Any is unnecessary, and seems to be explicitly being added in this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can surely figure out a way to remove it here.

But the logic of including it in general is this:

https://user-images.githubusercontent.com/32575566/119697648-6b188f00-be1e-11eb-91e3-c7ae8612f2e4.png

On the right you can count the arguments by counting the bold words. On the left the grey f, is really easy to miss, and seems worse than before.

Copy link
Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this seems sound. I have mentioned a few places to fix (to not change), but otherwise seems like it successfully shares code, so it quite sane.

@JeffBezanson
Copy link
Member

From triage: after much discussion, we all came to a different conclusion, that we like having the signature and location on two lines since (1) it's nice to be consistent with stack traces, and (2) the method location is actually always quite important and two lines makes it much easier to scan the locations. We also like omitting ::Any; it's less noisy and matches how we expect people to write such method signatures in their code.

Sorry about all the back-and-forth on this; these formatting PRs can be really contentious as you know :)

@mcabbott
Copy link
Contributor Author

Oh I know.

One question though is whether the all-important f fist argument goes missing too easily, light grey and no type. That's what the ::Any was trying to address. Another way might be to make all the arguments hot pink, a bit like #40251 (comment) . Strong feelings on this?

Fixing the code is easy, fixing the tests may take me a while to get to, we'll see.

@vtjnash
Copy link
Member

vtjnash commented Jan 20, 2022

Could we add some logic that prints either <light>::<bold> or <normal>, depending on whether there is content to print on the right-hand side?

@mcabbott
Copy link
Contributor Author

Yes, surely. Will try that out.

@tecosaur
Copy link
Contributor

Jeff/Jameson as a fan of making the arg name stand out regardless (as in #40251 (comment)), is there anything you have against that?

@vtjnash
Copy link
Member

vtjnash commented Jan 21, 2022

I mildly dislike the extra color, on the basis of being too noisy.

@vtjnash
Copy link
Member

vtjnash commented Jan 21, 2022

Note: closes #40913

@tecosaur
Copy link
Contributor

I mildly dislike the extra color, on the basis of being too noisy.

I see. I'd comment that having it be styled the same regardless is good for visual consistency and it isn't unimportant enough to be greyed out, as the naming can often be informative.

@JeffBezanson
Copy link
Member

Ah, I forgot to mention that @staticfloat made the point that we shouldn't rely too heavily on colors; the output should be nice even with the colors stripped. That's another reason I prefer leaving out ::Any.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
display and printing Aesthetics and correctness of printed representations of objects. REPL Julia's REPL (Read Eval Print Loop)
Projects
None yet
9 participants