`print` vs two-argument `show`/`repr` vs three-argument `show`/`repr` with `MIME"text/plain"`

I asked this question some time ago on slack, posting now the answers here to avoid the slackhole of oblivion.

Thanks to @jakobnissen, @Sukera, and @tecosaur for the information.

Question

When should I define methods for print and when should I define show(MIME"text/plain") ? I understand that bare two-argument show/repr are supposed to be “kinda parseable Julia-specific on best effort basis” and that print/string is supposed to be human readable (as print’s docstring says). But the show docstring also says to define a three-argument show(MIME"text/plain") method for human consumption (in addition to a two-argument show that is “kinda parseable”). My question would not really matter if print defaulted to show(MIME"text/plain") , but print actually defaults to the two-argument show .
So… should we consider changing the default print to be show(MIME"text/plain") ? If that default is not changed, should I be defining custom methods for print or for show(MIME"text/plain") when I want “human readable” representation?

Main Answer

These are three distinct methods, with slightly different meanings. They can be demonstrated using Char :

julia> print(stdout, 'P')
P
julia> show(stdout, 'P')
'P'
julia> show(stdout, MIME"text/plain"(), 'P')
'P': ASCII/Unicode U+0050 (category Lu: Letter, uppercase)

I.e.

  • print is the textual representation: What does this object look like when printed to the screen? Here, 'P' doesn’t have the ' symbols, it’s just printed as a plain P
  • Two-arg show is: “The simple representation in the REPL”, i.e. the parsable one. Essentially it’s the “Julia notation” for the object
  • Three-arg show is the fancy, bells and whistles representation for the repl which may span multiple lines and give extra info. That’s essentially a kind of “dump this object into the REPL”

Most types don’t need to define all three. For example:

  • String s need to distinguish between print and show (since only the latter includes the " marks), but not between two- and three-arg show, since there is no fancy representation of a string
  • Vector s don’t really have a textual printed representation - when printed, it just gives the “Julia notation”, i.e. the same as two-arg show. Because vectors are not really meant to be printed as strings. However, vectors do have a fancy three-arg show method (the one that begins with with “3-element Vector{Int}:”, then writes an element per line)
  • Int s need neither a fancy representation. nor a textual representation, so all three methods just end up in the same place

One more wrinkle is that, at least when I looked the last time, while the printing system is quite nice and flexible, it’s underdocumented, and in some cases even buggy with unstated assumptions that are violated elsewhere (very Julian). This has been an outstanding issue for many years but no-one has taken the time to clean it up

TLDR

if all you want is human readable representation, define the three arg show

Comments on use in the ecosystem

One issue with the 2 vs. 3 arg show and the way it’s done across the ecosystem is that it’s hard to have:

  • A pretty multi-line version (3-arg show)
  • A pretty compact version (3-arg show with compact IO)
  • A non-pretty repr-evaluable version (2-arg show)
  • The plain printing version (print)

The issue is with (2) the “pretty compact version”, which is often desirable when a value is to be displayed as part of another value (e.g. a Dict / Vector element, or some nested structure).It’s possible to handle this all correctly, but that requires consistent use of 3-arg show within other 3-arg show implementations, and consistent checking of io.compact. Unfortunately, neither are done consistently (edited)

My 2c: with hindsight it would have been best if different signatures were used for “short pretty” and “long pretty” methods

Previous discussion

14 Likes

I recently added a summary to the docs that should help (though I think all of this info was there before, albeit scattered): add summary of x::T output functions by stevengj · Pull Request #54547 · JuliaLang/julia · GitHub

You should mostly only define print methods for string/char-like types, for which there is a canonical “plain text” representation of the object that is distinct from how you input it into Julia. Dates are another example IIRC.

4 Likes

I once wrote a short blog post with an example of how I typically define my own custom show function which also goes into the compact mode for printing objects inside an array, maybe it’s helpful for this discussion.

I would actually be curious to see an overview of all possible IO options used in Julia base show functionality. So far I found compact and limit.

I don’t think you should be using :compact=>false with 2-argument show as a synonym for 3-argument/multi-line show.

With or without :compact, the 2-argument show should generally be a repr format that corresponds to Julia input. :compact is more about things like the number of digits to print. Your implementation, in contrast, mean that things like println(io, "x = ", x, ", y = ", y) will give a multi-line output for x::MyType if the :compact=>false flag is set on io.

Moreover, :compact=>false should be the default for both 2-argument and 3-argument show.

This is documented with IOContext (and is linked from my new summary in the manual above).

Thanks a lot for your reply! I see that I have to revisit my custom show approach.

But if I want to have a multiline display by default on the REPL, what would you consider the proper approach?

3-argument show is what is called by the REPL display. That’s the multiline version. 2-argument show is the repr(x) format.