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

A more nicely formatted output for the display() function with NamedTuples #50063

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

tomchor
Copy link
Contributor

@tomchor tomchor commented Jun 5, 2023

This is an attempt to create an output for display() with NamedTuples that's more human-readable.

My first attempt is an output that almost exactly matches that of Dicts:

julia> nt = (a=3, b=5, g=5, d=9,)
NamedTuple with 4 entries:
  a = 3
  b = 5
  g = 5
  d = 9

imo that could be good enough, and given that's virtually a copy-paste from Dict users should be familiar with it. But I'm hoping to have a discussion about it.

Note that in the output above I'm purposefully omiting the colon (: ) from the NamedTuple's keys' symbols to mimic the syntax of how NamedTuples are defined (i.e. we define it as nt = (a=3, b=5), not as nt = (:a=3, :b=5)).

Closes #50004

CC @gbaraldi

@andyferris
Copy link
Member

Oooh - interesting.

One of the reasons for the way it currently is, is that it mimics actual sytax - it is kind of like repr (but we don't need to do that for show let alone display; arrays also have built-in syntax but display this way). One thought experiment I would suggest considering here is could we, or should we, do a similar thing for Tuple? I generally like to think of tuples and named tuples as a kind of pair of similar things (modulo variance). What about the default display for structs? If you are exploring at the REPL that could be kind of nice...

One point in favour is one thing I love to do with named tuples is use them in aVector{NamedTuple{...}} as a lightweight table (or dataframe) structure. When you want to inspect a "row" of this table, the output is currently a little... condensed. Let's be honest - it's hard to read. This would definitely fix that. (In fact, I kind of wonder whether displaying the typing information would be helpful in such a context - even though this might be inferrable from the printed values it could pay to be explicit).

@mcabbott
Copy link
Contributor

it is kind of like repr

A possible style here would be:

julia> nt = (a=3, b=5, g=5, d=9,)
4-element NamedTuple(
  a = 3,
  b = 5,
  g = 5,
  d = 9,
)

julia> nt1 = (a=3,)
1-element NamedTuple(a = 3)

At present NamedTuple(; kw...) doesn't exist, but if it did, this would be copy-paste-able. The first line could also read NamedTuple{NTuple{4, Int64}}( to show the type.

Such printing could also be recursive, while remaining valid syntax. (I wrote something like this for Flux.jl, which in fact recurses into almost arbitrary structs.)

It's possible that very simple NamedTuples should not trigger the multi-line printing. Above I put a 1-element case on one line, that would be a simple rule... but it could also go by whether the 1-line printout fits into a terminal line, or something.

@stevengj
Copy link
Member

stevengj commented Jun 23, 2023

You could also do:

4-element NamedTuple
 (a = 3,
  b = 5,
  g = 5,
  d = 9)

which is readable but also gives you something copy-pasteable and tuple-looking.

@stevengj stevengj added the display and printing Aesthetics and correctness of printed representations of objects. label Jun 23, 2023
@tomchor
Copy link
Contributor Author

tomchor commented Jun 25, 2023

Thanks for the input everyone! And sorry for the delay in answering. Life is busy 😬

One thought experiment I would suggest considering here is could we, or should we, do a similar thing for Tuple? I generally like to think of tuples and named tuples as a kind of pair of similar things (modulo variance). What about the default display for structs? If you are exploring at the REPL that could be kind of nice...

One point in favour is one thing I love to do with named tuples is use them in aVector{NamedTuple{...}} as a lightweight table (or dataframe) structure. When you want to inspect a "row" of this table, the output is currently a little... condensed. Let's be honest - it's hard to read. This would definitely fix that. (In fact, I kind of wonder whether displaying the typing information would be helpful in such a context - even though this might be inferrable from the printed values it could pay to be explicit).

I personally don't see a problem with the way Tuples are shown since they're shown as kinda what they are: lists of sorts. My main issue with the way NamedTuples are displayed is that they're a bijection operation (technically surjection I guess? idk). So we have to be able to associate each key with its value, and that's pretty hard to do with the current display() method. That's not a problem I think with Tuples. That said, that's just my personal opinion, and I don't feel strongly about it :)

@tomchor
Copy link
Contributor Author

tomchor commented Jun 25, 2023

You could also do:

4-element NamedTuple
 (a = 3,
  b = 5,
  g = 5,
  d = 9)

which is readable but also gives you something copy-pasteable and tuple-looking.

I think I like this better because it looks like the actual syntax used to create the object and, like @stevengj points out, it's copy-pasteable and very readable.

@tomchor
Copy link
Contributor Author

tomchor commented Jun 26, 2023

For now I implemented something very close to @stevengj's suggestion (but with an extra colon).

Short NTs are displayed like:

julia> NamedTuple( Symbol(:El, el)=>el for el in 1:4 )
4-element NamedTuple:
 (El1 = 1
  El2 = 2
  El3 = 3
  El4 = 4)

And longer ones (that don't fit in the REPL's rows) are displayed like:

julia> NamedTuple( Symbol(:El, el)=>el for el in 1:11 )
11-element NamedTuple:
 (El1  = 1
  El2  = 2
  El3  = 3
  El4  = 4
  El5  = 5
  El6  = 6
  El7  = 7
  El8  = 8
  El9  = 9
      =  )

(Note that the last line (the one with \vdots is aligned in the REPL, but for some reason appears misaligned on github.)

Thoughts?

@mcabbott
Copy link
Contributor

Trying this out:

julia> (a=1,)  # needs the trailing comma
1-element NamedTuple:
 (a = 1)

julia> (a=1, bb=(x=10, yy=20), ccc_ccc=3)  # should this be recursive?
3-element NamedTuple:
 (a       = 1
  bb      = (x = 10, yy = 20)
  ccc_ccc = 3)

julia> NamedTuple(Symbol(i) => i^2 for i in 1:100)  # symbols broken
100-element NamedTuple:
 (ymbol("1") = 1
  ymbol("2") = 4
  ymbol("3") = 9
            =  )
  • Justification follows what e.g. Dict(:a => 1, :bb => (x=10, yy=20), :ccc_ccc => 3) does, but not what e.g. :(a=1; bb=2; ccc_ccc=3) does.

  • Truncation with also follows Dict, rather than Tuple. But perhaps you should not be encouraged to use a NamedTuple for so many fields that this is a concern?

  • Nested NamedTuples are common. Should the printing recurse to unfold inner ones? It could be extensible for custom structs to opt in, a bit like showarg.

  • Should the first line show eltype, as this is sometimes useful? As e.g. [1,2,3]' does. Dict prints the whole typeof but probably that's too long & repeats too much of what's printed anyway.

@vtjnash
Copy link
Member

vtjnash commented Jun 26, 2023

A lot of those questions seem better suited to reasons to use the PrettyPrinting package, though perhaps that should be what the REPL does, it is different direction to a solution.

@andyferris
Copy link
Member

andyferris commented Jun 27, 2023

Truncation with also follows Dict, rather than Tuple. But perhaps you should not be encouraged to use a NamedTuple for so many fields that this is a concern?

This is a good point. There could be various (e.g. performance, egonomics) issues with riduclously large named tuples; it seems pretty-printing a few too many lines to the REPL then would be the least of your issues.

(OTOH it is trivial to type something like zeros(2^32), and not truncating with for dynamically sized collections would be a minor disaster)

@tomchor
Copy link
Contributor Author

tomchor commented Jun 28, 2023

I agree with these points. I think for nice recursiveness/unfolding and non-truncating printing, PrettyPrinting is the obvious choice.

In my mind, regarding this specific PR, the question is whether it's worth to implement a more simplified solution (i.e. one that does not unfold inner NamedTuples and that truncates) so that a user can still get a reasonable human-readable output in the REPL without installing an additional packages.

For me, personally, the answer is yes, it's worth it. Especially since afak PrettyPrinting isn't an official Julia package (i.e. it's not hosted by JuliaLang, or JuliaMath, etc.) and since (like @andyferris mentioned).

Thoughts?

PS.: obviously some of the points highlighted by @mcabbott still need to be resolved first.

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.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

A more nicely formatted output for the display() function with NamedTuples
5 participants