Julia: Printing of time-related types in containers is inconsistent

Created on 30 Jan 2019  ·  66Comments  ·  Source: JuliaLang/julia

I think #30817 changed show(io, "text/plain", ::Array{Day}) etc. On cb7a5691a51b6bbc0e670f601d6269d893340e44 (and in Julia 1.0 and 1.1) it was:

julia> Day.(1:3)
3-element Array{Day,1}:
 1 day
 2 days
 3 days

But now (c0e9182fa94e2b96106e8e4c14cb36dc9d35cb40) it's:

julia> Day.(1:3)
3-element Array{Day,1}:
 Day(1)
 Day(2)
 Day(3)

Furthermore, confusingly (to me), Matrix{Day} still prints human readable form:

julia> [Day(1) Day(2)]
1×2 Array{Day,2}:
 1 day  2 days

However, @omus and others approved the following behavior in #30200:

julia> fill(d, 2)
2-element Array{Date,1}:
 Date(2018, 12, 7)
 Date(2018, 12, 7)

julia> fill(d, 2, 2)
2×2 Array{Date,2}:
 2018-12-07  2018-12-07
 2018-12-07  2018-12-07

So maybe the current behavior of Vector{Day} and Matrix{Day} is intentional? (But, to be honest, I was confused why ndims of an array changes how elements are printed.)

Another confusing part is that show(io, ::Array{Day}) still shows human-readable form. Shouldn't it closer to repr than 3-argument show?

julia> show(Day.(1:2))
Day[1 day, 2 days]

Note that the output of Dict{Day,Day} and Vector{Pair{Day,Day}} are not changed. It has been (and is):

julia> Dict(Day(1) => Day(2))
Dict{Day,Day} with 1 entry:
  1 day => 2 days

julia> [Day(1) => Day(2)]
1-element Array{Pair{Day,Day},1}:
 1 day => 2 days

What is the intended printing behavior of Day, Date, and alike in the containers?

See also: #30683

dates display and printing

Most helpful comment

ok cool, this is my current working output. I'm thinking this satisfies all if not most of the discussion here. Hopefully if this makes people happy, I can move on to getting the other time/date types feeling consistent with this, and I'll open up a PR.

julia> using Dates
julia> d = Day(1)
1 day
julia> show(d)
Day(1)
julia> show([d])
[Day(1)]
julia> show([d d])
[Day(1) Day(1)]
julia> show(IOContext(stdout, :compact=>true), [d])
[Day(1)]
julia> show(IOContext(stdout, :compact=>false), [d])
[Day(1)]
julia> [d, d][1]
1 day

julia> fill(d, 2)
2-element Array{Day,1}:
 1 day
 1 day

julia> fill(d, 2, 2)
2×2 Array{Day,2}:
 1 day  1 day
 1 day  1 day

julia> print(d)
1 day
julia> string(d)
"1 day"

julia> repr(d)
"Day(1)"

julia> fill(d => d, 2)
2-element Array{Pair{Day,Day},1}:
 Day(1) => Day(1)
 Day(1) => Day(1)

julia> fill(d => d, 2, 3)
2×3 Array{Pair{Day,Day},2}:
 Day(1)=>Day(1)  Day(1)=>Day(1)  Day(1)=>Day(1)
 Day(1)=>Day(1)  Day(1)=>Day(1)  Day(1)=>Day(1)

All 66 comments

Matrix printing sets :compact=>true, vector printing doesn't. And indeed since https://github.com/JuliaLang/julia/pull/30817 show checks for that property to decide the printing format. That's doesn't make a lot of sense to me since both representations take the same number of characters. What @JeffBezanson suggested in that PR was to change repr to use Day(1), but still print 1 day from show, but that's not what's been implemented.

What we could also do is check the :typeinfo property of IOContext, and if it's equal to Day, print only the number, without the mention of the unit. That would give a super compact printing in most arrays and in data frames.

Cc: @fchorney

We should remove the branch that switches to 1 day based on the :compact property. We can optionally add typeinfo support as well.

@tkf Would you make a pull request?

I think I can make a PR, but I need to understand the desired output.

If we check :typeinfo and only print a number, show(Day.(1:2)) would prints Day[1, 2], right? But this can't be evaluated now because Base.convert(::Type{Day}, ::Int) is not defined. Should we still print Day[1, 2] here?

Branching on :compact seems to be added to show human-readable format in DataFrame https://github.com/JuliaLang/julia/pull/30200#issuecomment-444279588. But my guess is that the fix should go into DataFrames.jl. It seems that DataFrames.jl is using 2-arg show to pretty-print the element: https://github.com/JuliaData/DataFrames.jl/blob/8d75d7464bdea76bb394081f66d870b2d9bf1c3b/src/abstractdataframe/show.jl#L20-L30 I suppose it should be using show(IOContext(io, :compact=>true, :typeinfo=>typeof(x)), "text/plain", x). So, is it OK to temporary break DataFrame printing?

Likewise, Base.print_matrix_row also uses 2-arg show for elements even though print_matrix_row is called from 3-arg show for an array.

https://github.com/JuliaLang/julia/blob/62c411d4defca4ca41f72468f98a223ada8442e4/base/arrayshow.jl#L104

It seems to me that it should be using 3-arg show with :compact => true. This also applies to other container types like Dict.

(All my above comments are based on the assumption that changing show is considered a "minor change".)

I guess we can defer the :typeinfo change for now, since convert(::Type{Day}, ::Int) may be controversial. Though I'm not sure whether the output of show is always supposed to be parseable (I guess @JeffBezanson knows?).

Regarding the two-argument vs. three-argument show, AFAIK arrays should use the two-argument method, as the three-argument one is for multiline printing.

Though I'm not sure whether the output of show is always supposed to be parseable (I guess @JeffBezanson knows?).

I already asked him this in #30757. His answer (https://github.com/JuliaLang/julia/pull/30757#issuecomment-456973443) was:

I should add that we are not really strict about show output needing to be parseable. I think the reason for that is that I don't really see many use cases for parseable output, but I could be wrong. If there are contexts where parseable output is really important, we indeed might need to add a new thing for guaranteed parseable output.

So maybe Day[1, 2] is OK?

two-argument vs. three-argument show

I think there are three concepts and only two methods. Ideally, it's nice to have:

  • (a) Human-readable multi-line text representation.
  • (b) Human-readable short text representation (which is used by containers).
  • (c) Parse-able or at least Julia code-like (?) text representation.

3-arg show does (a) and 2-arg show has to do (c) as that's how repr works. The question is what function should do (b). If we let 2-arg show do (b) and (c), it's very tricky to have nice 3-arg show for Array{MyType} (see e.g., https://discourse.julialang.org/t/18951). That's why I suggested in https://github.com/JuliaLang/julia/pull/30757#issuecomment-456936172 to add MIME"application/julia" to the convention so that we can decouple (b) and (c). Alternatively, as I mentioned in the previous comment, letting 3-arg show handle (b) with :compact => true would also decouple them.

TBH I'd rather not discuss the redesign of the printing system here as it's really beyond the scope of this issue. Feel free to file another issue if you have a detailed proposal.

So maybe Day[1, 2] is OK?

Why not. Though as a data point, Unitful.jl represents arrays of values with units like this (which is not parseable):

julia> show(fill(1Unitful.m, 2))
Quantity{Int64,𝐋,Unitful.FreeUnits{(m,),𝐋,nothing}}[1 m, 1 m]

So the equivalent would be "[1 day, 1 day]". It would make sense to use the same convention in Base and in Unitful (in either direction). Cc: @ajkeller34

So maybe Day[1, 2] is OK?

I personally don't like this as it appears to be valid Julia code but it isn't. Since we already decided that convert(::Type{Day}, ::Integer) is a non-sensical conversion we may want to instead use Day.([1, 2]) as it is almost as terse but is parsable. I'm also fine with [1 day, 2 days].

Branching on :compact seems to be added to show human-readable format in DataFrame https://github.com/JuliaLang/julia/pull/30200#issuecomment-444279588. But my guess is that the fix should go into DataFrames.jl

I like this idea. Can we implement the fix for DataFrames first?

I personally don't like this as it appears to be valid Julia code but it isn't.

Agree.

Actually I will partly retract my previous comment --- with the printing system as it is now, using :compact to select the human-readable output for show seems like a reasonable solution. I think most of the blame here falls on the fact that 3-arg show for 1-d arrays doesn't touch :compact, but all other ways of printing arrays set :compact=>true. I have actually never liked that. repr and show should not set :compact --- I think that's a bug --- and I'd prefer for 3-arg show to always set it.

@nalimilan
My apologies. It was my understanding that the output was fine as far as print, string, repl, and show were concerned, except that we still wanted “1 day” to appear in the REPL when declared.

we may want to instead use Day.([1, 2]) as it is almost as terse but is parsable.

We can't print Day.([1, 2]) since we only control the printing of individual entries here.

Branching on :compact seems to be added to show human-readable format in DataFrame #30200 (comment). But my guess is that the fix should go into DataFrames.jl

I like this idea. Can we implement the fix for DataFrames first?

What do you want to fix in DataFrames? AFAICT it uses show just like printing vectors does.

What do you want to fix in DataFrames?

At the moment nothing needs to change. If we decide to move away from using the compact show to display the human-readable output then it would be good to adjust DataFrames behaviour so it can continue to show the human-readable output.

I don't think DataFrames should diverge from vector printing.

I personally don't like this as it appears to be valid Julia code but it isn't.

Agree.

@JeffBezanson So show([Day(1)]) should print Day[Day(1)]?

we may want to instead use Day.([1, 2]) as it is almost as terse but is parsable.

We can't print Day.([1, 2]) since we only control the printing of individual entries here.

@nalimilan In principle I think we can add an API (say) convertiblerawvalue(::Type) :: Bool so that containers can use broadcast syntax to implement show. Maybe that's too complicated, though.

So show([Day(1)]) should print Day[Day(1)]?

Yes, that's the best we can do. I guess [Day(1)] would be a bit better.

Is [Day(1)] possible with current API? Isn't it as difficult as Day.([1])?

@JeffBezanson Regarding:

I have actually never liked that. repr and 2-arg show should not set :compact --- I think that's a bug --- and I'd prefer for 3-arg show to always set it.

("2-arg" added by me)

A downside of this approach is that :compact is used sometimes really for "compact" output, not just for toggling human-readability. If we apply this to all containers, show(Dict(0=>1)) would print Dict(0 => 1) (with spaces) instead of Dict(0=>1) (current output). I guess that's not desirable?

https://github.com/JuliaLang/julia/blob/0ec17276a6055fea31971eaad109d5888b9edd16/base/show.jl#L592-L601

:man_shrugging: Dict(0 => 1) seems fine to me.

Seems much more readable to me.

@JeffBezanson So do you think we need to change something here? Better not release 1.2 with a new behavior if we want to change it again in 1.3.

In particular, it seems weird to me that the 3-argument show gives the same result as the 2-argument show with :compact=>true. Indeed the manual says that the 3-argument method is for "verbose multi-line printing", so it should rather be similar to :compact=>false.

julia> show(stdout, "text/plain", Day(1))
1 day
julia> show(IOContext(stdout, :compact=>false), Day(1))
Day(1)
julia> show(IOContext(stdout, :compact=>true), Day(1))
1 day

julia> show(stdout, "text/plain", Date(2018, 12, 7))
2018-12-07
julia> show(IOContext(stdout, :compact=>false), Date(2018, 12, 7))
Date(2018, 12, 7)
julia> show(IOContext(stdout, :compact=>true), Date(2018, 12, 7))
2018-12-07

Maybe show should always use the "compact" form if we find it preferable for the 3-argument method?
EDIT: I now see that the problem is that repr is supposed to call show and we shouldn't define repr methods. Ah, well... maybe we don't really need repr to use the Julia syntax?

One thing happening here is that printing like 1 day is fine until we are nested inside something that prints using julia syntax like [ ] or =>. Arguably we should never print anything like [1 day, 2 days] or 1 day=>2 days. If people agree with that principle, the solution that comes to mind is to add the :parseable flag, and set it when repr calls show and whenever we nest inside pairs/arrays/etc. Adding a new IOContext property should be non-breaking (except, of course, where we'll use it to fix this issue and change the output).

Having something like :precedence seems like it could be nice, such that items that know they normally skip printing of ambiguity-resolving delimiters (like Date and Pair) could alter the printing of themselves to add parens or other such modifications. This seems like it might also be better than the current strategy of Pair to conditionally add parens to its arguments based on its complicated, static internal model of how it predicts the component arguments will print.

I think using a new MIME like show(::IO, ::MIME"application/julia", ::T) for repr is better than IOContext because you can make sure to call printer that is intended to be parseable. IOContext-based API basically means "may or may not be parseable" for a caller.

See also #30757

A new MIME type might be ok but I think it solves a different problem. The problem here is that this output is weird:

julia> fill(Day(1) => Day(2), 2, 2)
2×2 Array{Pair{Day,Day},2}:
 1 day=>2 days  1 day=>2 days
 1 day=>2 days  1 day=>2 days

The context (REPL display) does not require parseable output, it's just that 1 day=>2 days looks terrible.

OK I think I get caught up the name :parseable. I guess it just means "called inside a container-like object"? Then maybe checking the _existence_ of :typeinfo does the job? That is to say, Day(1) is printed as

  • 1 if :typeinfo is set to Day
  • Day(1) if :typeinfo is set to other types
  • 1 day otherwise

That's an interesting idea. I believe the main downside is that it won't give us this output back:

julia> Day.(1:3)
3-element Array{Day,1}:
 1 day
 2 days
 3 days

if that's what we want.

I think the output in my proposal

julia> Day.(1:3)
3-element Array{Day,1}:
 1
 2
 3

is compatible with the way Array{Bool} uses 0/1 #30575.

Except that false == 0 holds but Day(1) == 1 does not, so it's not quite the same situation.

Ah, yes. I also realized that my proposal is (somewhat?) problematic since it'd do

julia> show(Day.(1:2))
Day[1, 2]

But Day[1, 2] is invalid (even though it is parseable) since convert(::Type{Day}, ::Int) is not defined (as already discussed).

IIRC, there is no way for Day to behave differently inside show(::IO, ::Array{Day}) and show(::IO, ::MIME"text/plain", ::Array{Day}) because show(::IO, ::Day) is used inside both of them https://github.com/JuliaLang/julia/issues/30901#issuecomment-459142574. It would be nice if 2-arg and 3-arg show worlds are clearly separated so that each type T has some control of printing of "Container{T}" in 2-arg and 3-arg show. In 3-arg show of containers, I think using show(IOContext(io, :compact=>true, ...), "text/plain", x) to print element x in the container makes more sense than using 2-arg show.

I think the tradeoff there is that the text/plain output for 1-d arrays sets :compact=>false, so the elements of vectors-of-vectors would use the long multi-line format, which doesn't really work. Basically you'd have to set :compact=>true for everything inside containers, which is quite inflexible.

Overall the problem here is that we use features of the printing system for not-quite their intended meanings. E.g. 1 day is not a "compact" form of Day(1), it's just a totally different style of printing. A secondary problem is ad-hoc decisions like printing matrix elements compactly, but not vector elements.

Let me take back the comment about "clearly separating 2-arg and 3-arg show worlds." I think reducing human readability as you go inside nested object like show(::IO, ::MIME"text/plain", ::Vector{Vector{Day}}) -> show(::IO, ::Vector{Day}) -> "repr(::IO, ::Day)" (or application/julia) would make sense. Maybe not being able to do something like this was what you meant by "quite inflexible?"

Overall the problem here is that we use features of the printing system for not-quite their intended meanings.

I'd like to add that the intended meanings have been unclear (at least to me). It looks like 2- and 3-arg show are used for

  1. Multi-line/verbose output
  2. Single-line/terse output
  3. Parseable/evaluatable output

But it's not possible to consistently choose one of those at the moment. I think this lack of consistency leads to problems like inability to choose 1 day or Day(1) and also:

A secondary problem is ad-hoc decisions like printing matrix elements compactly, but not vector elements.

Idea from triage on the fastest way to resolve this for 1.2: use 1 day only for the MIME"text/plain" format, and tell DataFrames to call that.

Also noted that Unitful.jl has the same problem, printing 1 meter as 1 m. But, it uses that printing everywhere, so at least it's consistent.

We also discussed calling a new flag for this :repr. In the future, I think it would be good to have so we can print 1 day inside arrays but Day(1)=>Day(2) inside pairs etc.

Looking back at @nalimilan 's comment

That's doesn't make a lot of sense to me since both representations take the same number of characters.

makes me think the problem might really be that simple. For example

julia> a = fill(now(),3)
3-element Array{DateTime,1}:
 DateTime(2019, 4, 25, 15, 41, 32, 171)
 DateTime(2019, 4, 25, 15, 41, 32, 171)
 DateTime(2019, 4, 25, 15, 41, 32, 171)

julia> repeat(a,1,2)
3×2 Array{DateTime,2}:
 2019-04-25T15:41:32.171  2019-04-25T15:41:32.171
 2019-04-25T15:41:32.171  2019-04-25T15:41:32.171
 2019-04-25T15:41:32.171  2019-04-25T15:41:32.171

is also inconsistent, but you forgive it since the compact format really is more compact. While 1 day is not smaller than Day(1) so it's not clear why we'd switch to that. So maybe this only applies to periods, or maybe we should just do nothing?

you forgive it since the compact format really is more compact

Well, I personally never "forgive" it :wink:. I've been using Day as it's easier to construct. I think it's better to have a consistent treatment for all time-related types (or rather _any_ types).

For example, we currently have:

julia> show([1=>2]); println()
Pair{Int64,Int64}[1 => 2]

julia> show(IOContext(stdout, :compact=>true), [1=>2]); println()
Pair{Int64,Int64}[1=>2]

However, Vectpr{Date} behaves differently

julia> show([Date(2000, 01, 01)]); println()
Date[Date(2000, 1, 1)]

julia> show(IOContext(stdout, :compact=>true), [Date(2000, 01, 01)]); println()
Date[2000-01-01]

If we want Date[Date(2000,1,1)] to be in the last output, we need to avoid switching behavior of show(::IO, ::Date) based on :compact. Or maybe Date[2000-01-01] is what we want?

the current strategy of Pair to conditionally add parens to its arguments based on its complicated, static internal model of how it predicts the component arguments will print.

FWIW: the system used by Pair is not so static: isdelimited(io::IO, x::X), if defined, indicates whether parenthesis are needed around the output of show(io, x) in ambiguous situations. So isdelimited has access to the current IOContext to make its decision. So it's an extensible and hopefully not so complicated system, which doesn't have to be restricted to pairs (although I'm certainly not claiming it's a good enough general system that doesn't need a redesign).

So with the current implementation of show for Day, the following could be defined:

julia> Base.isdelimited(io::IO, x::Day) = !get(io, :compact, false)

julia> fill(Day(1) => Day(2), 2)
2-element Array{Pair{Day,Day},1}:
 Day(1) => Day(2)
 Day(1) => Day(2)

julia> fill(Day(1) => Day(2), 2, 3)
2×3 Array{Pair{Day,Day},2}:
 (1 day)=>(2 days)  (1 day)=>(2 days)  (1 day)=>(2 days)
 (1 day)=>(2 days)  (1 day)=>(2 days)  (1 day)=>(2 days)

(PS: I would remove the :compact check when showing Day which indeed doesn't seem to have to do with compact printing)

Idea from triage on the fastest way to resolve this for 1.2: use 1 day only for the MIME"text/plain" format, and tell DataFrames to call that.

@JeffBezanson I don't understand why DataFrames keeps coming up in this thread. It just uses the same printing as matrices (i.e. 2-argument show with :compact=>true. It wouldn't make sense to switch to the three-argument text/plain method AFAICT since it's supposed to be used for multi-line printing.

So maybe this only applies to periods, or maybe we should just do nothing?

What do you mean by "do nothing"? Keep the current state of master, or go back to 1.1?

Overall I agree that :compact shouldn't be used for radical changes to the printing format like this: it should just change the spacing, the number of digits or the amount of detail printed.

I don't understand why DataFrames keeps coming up in this thread.

It's just an example of a case where the 1 day output might be desired.

What do you mean by "do nothing"?

I mean keep the current state of master.

(PS: I would remove the :compact check when showing Day which indeed doesn't seem to have to do with compact printing)

Yes, that might be the best option. 1 day is not any particularly standard format, so we might as well use Day(1). It's different for other time types, e.g.

julia> now()
2019-04-28T14:00:39.635

julia> repr(now())
"DateTime(2019, 4, 28, 14, 0, 46, 84)"

where there is a significant advantage to using the former output e.g. in dataframes.

While the issue here only needs to deal with the question for dates, I have another type that ran into the same sorts of problems: Polynomial. Notably (with respect to my musing that "parsable" is actually "precedence"), I found that this package currently uses that idea somewhat explicitly: https://github.com/JuliaMath/Polynomials.jl/blob/96b9a210776d6b212fb21df38d8b07db1fd1e629/src/show.jl#L177

Another datapoint, we see that Complex treats the ":compact" flag as meaning it should assume that it's printed inside an array (similar to Pair, dropping the space under the assumption that makes it visually unambiguous)

But then we also see that Polynomials had to intercept the printing for Complex because of this: https://github.com/JuliaMath/Polynomials.jl/blob/master/src/show.jl#L185, and has an example using Dual numbers and precedence showing how to extend it further.

It's different for other time types

That does seem bad. I think my preference here would be of a third option, which could be one of:

DateTime(2019-04-28T14:00:39.635)
‘2019-04-28T14:00:39.635’
DateTime(Y=2019, m=4, d=28, H=14, M=0, S=39, s=635)

which is not necessarily parsable or directly useable (or more compact), but any of which could satisfy the precedence expectation of this show context(*). (I think the first is better for this type, but include the others as examples of what I think other types might instead choose to do in the similar situation)

I think the first is better for this type, but include the others as examples of what I think other types might instead choose to do in the similar situation

My understanding is that what you want to do is to make the "tree" structure of the object explicit even if it is not parsable. If so, ‘2019-04-28T14:00:39.635’ or even (2019-04-28T14:00:39.635) sounds like a good option. That is to say, the :precedence is used for clarifying the "object boundary", not for clarifying the object type.

Changes reverted on the backports branch. Moving to 1.3.

@nalimilan, wasn't reverting the incomplete changes your idea?

Yes -- I clicked on the wrong button. :-)

From triage: since we have been reverting this for every release, we should probably revert it on master. Then at some point we can try to implement printing as date"0000-00-00" (keeping 0000-00-00 in compact mode). Not as clear how to handle Day(1) though.

I was hoping to tackle this myself at some point soon. Though ideally we can come to a consensus about what we want with the period types and I would be happy to do the work.

Would something like this at least feel consistent?
Doing something like this wouldn't require us to make a new date_str.

That being said, I am still unsure what we would want the solution for the 1 day=>1 day formatting issue to be.

----
julia> d = Date("2019-01-02")
2019-01-02
julia> show([d])
Date[Date("2019-01-02")]
julia> show([d d])
Date[Date("2019-01-02") Date("2019-01-02")]
julia> show(IOContext(stdout, :compact=>true), [d])
Date[2019-01-02]
julia> show(IOContext(stdout, :compact=>false), [d])
Date[Date("2019-01-02")]
julia> fill(d, 2)
2-element Array{Date,1}:
 Date("2019-01-02")
 Date("2019-01-02")
julia> fill(d, 2, 2)
2×2 Array{Date,2}:
 2019-01-02  2019-01-02
 2019-01-02  2019-01-02
julia> fill(d => d, 2)
2-element Array{Pair{Date,Date},1}:
 Date("2019-01-02") => Date("2019-01-02")
 Date("2019-01-02") => Date("2019-01-02")
julia> fill(d => d, 2, 3)
2×3 Array{Pair{Date,Date},2}:
 2019-01-02=>2019-01-02  2019-01-02=>2019-01-02  2019-01-02=>2019-01-02
 2019-01-02=>2019-01-02  2019-01-02=>2019-01-02  2019-01-02=>2019-01-02
julia> print(d)
2019-01-02
julia> repr(d)
"Date(\"2019-01-02\")"

DateTime
--------
julia> d = DateTime("2019-01-01T01:01:01.1")
2019-01-01T01:01:01.1
julia> show([d])
DateTime[DateTime("2019-01-01T01:01:01.1")]
julia> show([d d])
DateTime[DateTime("2019-01-01T01:01:01.1") DateTime("2019-01-01T01:01:01.1")]
julia> show(IOContext(stdout, :compact=>true), [d])
DateTime[2019-01-01T01:01:01.1]
julia> show(IOContext(stdout, :compact=>false), [d])
DateTime[DateTime("2019-01-01T01:01:01.1")]
julia> fill(d, 2)
2-element Array{DateTime,1}:
 DateTime("2019-01-01T01:01:01.1")
 DateTime("2019-01-01T01:01:01.1")
julia> fill(d, 2, 2)
2×2 Array{DateTime,2}:
 2019-01-01T01:01:01.1  2019-01-01T01:01:01.1
 2019-01-01T01:01:01.1  2019-01-01T01:01:01.1
julia> fill(d => d, 2)
2-element Array{Pair{DateTime,DateTime},1}:
 DateTime("2019-01-01T01:01:01.1") => DateTime("2019-01-01T01:01:01.1")
 DateTime("2019-01-01T01:01:01.1") => DateTime("2019-01-01T01:01:01.1")
julia> fill(d => d, 2, 3)
2×3 Array{Pair{DateTime,DateTime},2}:
 2019-01-01T01:01:01.1=>2019-01-01T01:01:01.1  2019-01-01T01:01:01.1=>2019-01-01T01:01:01.1  2019-01-01T01:01:01.1=>2019-01-01T01:01:01.1
 2019-01-01T01:01:01.1=>2019-01-01T01:01:01.1  2019-01-01T01:01:01.1=>2019-01-01T01:01:01.1  2019-01-01T01:01:01.1=>2019-01-01T01:01:01.1
julia> print(d)
2019-01-01T01:01:01.1
julia> repr(d)
"DateTime(\"2019-01-01T01:01:01.1\")"

Periods
-------
julia> d = Day(1)
1 day
julia> show([d])
Day[Day(1)]
julia> show([d d])
Day[Day(1) Day(1)]
julia> show(IOContext(stdout, :compact=>true), [d])
Day[1 day]
julia> show(IOContext(stdout, :compact=>false), [d])
Day[Day(1)]
julia> fill(d, 2)
2-element Array{Day,1}:
 Day(1)
 Day(1)
julia> fill(d, 2, 2)
2×2 Array{Day,2}:
 1 day  1 day
 1 day  1 day
julia> print(d)
1 day
julia> repr(d)
"Day(1)"
julia> fill(d => d, 2)
2-element Array{Pair{Day,Day},1}:
 Day(1) => Day(1)
 Day(1) => Day(1)
julia> fill(d => d, 2, 3)
2×3 Array{Pair{Day,Day},2}:
 1 day=>1 day  1 day=>1 day  1 day=>1 day
 1 day=>1 day  1 day=>1 day  1 day=>1 day

Isn't that exactly what was implemented before --- using the :compact flag to select 1 day instead of Day(1)?

For that specific type yes. I thought the main issue was that the printing of things wasn't necessarily consistent, so maybe I misunderstood. My example above also changes how Date and DateTime is printed in various situations, which seemed to be consistent among the 3 types (as far as printing a "compact" vs "not compact" output goes).

From what I understand, perhaps there are two issues.

  1. The fact that the different date/time types weren't consistent on when they printed their compact vs non compact forms.
  2. Changing the formatting of the "compact" form for the period types as it looks quite ugly in certain situations. Or rather, making a new formatting flag to designate when we'd want the 1 day kind of output.

Please correct me if I am misunderstanding some aspect of this. I'm trying to wrap my head around what we want the end goal to be, so that I can jump in and start working on the solution.

Just a small point:

julia> show(IOContext(stdout, :compact=>true), [d])
Date[2019-01-02]
julia> show(IOContext(stdout, :compact=>false), [d])
Date[Date("2019-01-02")]

I don't think this difference is justified: we have :typeinfo to decide whether the type information should be repeated, :compact=>false isn't supposed to enable the printing of redundant information.

IMO, the goal should be for repr and show output to always be parseable. Plus ideally, whenever these occur in a "syntaxy" context, e.g. inside =>, they should also be parseable. So 1 day can appear as an array element by itself, but 1 day => 1 day should never be printed.

This issue also complains about the difference in printing between vectors and matrices. Ideally they should be the same; that can be done by either (1) turning :compact on for both, or (2) use something other than :compact to pick the 1 day representation.

The only contexts I can think of where 1 day (etc.) makes sense are directly at the prompt (e.g. show with a MIME type), or as a direct array/dataframe element. :compact is not ideal for that because it propagates. So maybe we need a new showelement function that defaults to just calling show with compact set. Date and time types can overload that. Adding a new function sounds extreme, but it looks like the easiest way to get these properties without needing to change lots of code (e.g. changing many types to set a :syntax flag).

showelement sounds good.

Should showelement call 2-arg show? Or 3-arg show? I think it should be 3-arg show as 2-arg show can be large if we impose that it is parseable. But it would then mean that we need to define showelement for containers to cleanly show something like vector of vectors.

ok I think I'm starting to understand. Thanks!
So let's just look at Period types for now. Would this be the output that makes sense?

julia> d = Day(1)
1 day

julia> show(d)
Day(1)

julia> showelement(d)
1 day

julia> show([d])
Day[1]

julia> show([d d])
Day[1 1]

julia> show(IOContext(stdout, :compact=>true), [d])
Day[1]

julia> show(IOContext(stdout, :compact=>false), [d])
Day[1]

julia> [d, d][1]
1 day

julia> fill(d, 2)
2-element Array{Day,1}:
 Day(1)
 Day(1)

julia> fill(d, 2, 2)
2×2 Array{Day,2}:
 Day(1)  Day(1)
 Day(1)  Day(1)

julia> print(d)
1 day

julia> string(d)
"1 day"

julia> repr(d)
"Day(1)"

julia> fill(d => d, 2)
2-element Array{Pair{Day,Day},1}:
 Day(1) => Day(1)
 Day(1) => Day(1)

julia> fill(d => d, 2, 3)
2×3 Array{Pair{Day,Day},2}:
 Day(1)=>Day(1)  Day(1)=>Day(1)  Day(1)=>Day(1)
 Day(1)=>Day(1)  Day(1)=>Day(1)  Day(1)=>Day(1)

I guess Im having an issue with the whole

julia> show([d])
Day[1]

since it seems like above people don't really like this as it looks like parsable julia code but isn't (yet). In the same vain, it seems like people also don't like:

julia> show([d])
Day[Day(1)]

since it duplicates the type.

julia> fill(d, 2)
2-element Array{Day,1}:
 Day(1)
 Day(1)

julia> fill(d, 2, 2)
2×2 Array{Day,2}:
 Day(1)  Day(1)
 Day(1)  Day(1)

Those would call showelement, and so could print 1 day. I agree with all the other cases, except Day[Day(1)] doesn't bother me. Repeating type names is better than the situation today IMO.

We could allow overloading another function (Base calls it typeinfo_implicit) to indicate that a type's representation fully indicates the type. Then we could at least show a Day array as [Day(1), Day(2)].

What about display([fill(d, 10^3)])?

I think that would look like

1-element Array{Array{Day,1},1}:
 Day[Day(1), Day(1) ... Day(1), Day(1)]

Would that be handled by 2-arg show via showelement? Shouldn't showelement call 3-arg show as you can't guarantee evaluable output with :compat/:limit? In that case, I think

1-element Array{Array{Day,1},1}:
 [1, 1, …, 1, 1]

or even

1-element Array{Array{Day,1},1}:
 [1 day, 1 day, …, 1 day, 1 day]

is possible.

So maybe we need a new showelement function that defaults to just calling show with compact set.

I suppose :limit as well, so that large objects can use .

Currently array elements use 2-argument show, so I think showelement should call that. 3-argument show is more intended for displaying an object by itself, not inside another.

I think :compact output can still be parseable, you might just lose some information. :limit obviously won't be parseable, but all it should do is replace elements with .... Something like [1 day, 1 day, …, 1 day, 1 day] might be possible, but not appealing to me and not easy to achieve either. As soon as the [ is printed we're in syntax mode, and we will naturally ask for parseable representations of all the elements.

I suppose :limit as well, so that large objects can use .

No, :limit should basically only be set at the top level. If objects set it in their show methods, then you might get stuck with no way to see the entire contents of something.

Currently array elements use 2-argument show, so I think showelement should call that.

Wasn't this the reason why we don't have parsable repr? People were forced to misuse 2-arg show for printing for-human text outputs to customize display(::Vector{<:Mytype}). I think using 3-arg show for default is an effective (though painful) strategy for propagating this change to downstreams.

If you say that only the call signature is the public API of show and not the detail of output (which practically has been the case in 1.x), this is non breaking.

If objects set it in their show methods, then you might get stuck with no way to see the entire contents of something.

Thanks for pointing it out. I totally missed this point.

I think using 3-arg show for default is an effective (though painful) strategy for propagating this change to downstreams.

Interesting, I think I see what you mean --- for values like days, 3-argument show would print the desired 1 day inside arrays. But in general, 3-argument show can be very free-form, and often contains multiple lines, so it is not really the right thing to use. Then again maybe 3-argument show with :compact set is OK --- that says "we're inside display, so output can be free-form, but please don't use multiple lines since you're inside an array". Is that what you're thinking?

In fact, 3-arg show is never(?) called recursively, so we could potentially use :compact 3-arg show instead of adding showelement.

That's exactly what I'm thinking!

We could allow overloading another function (Base calls it typeinfo_implicit) to indicate that a type's representation fully indicates the type. Then we could at least show a Day array as [Day(1), Day(2)].

So I'm continuing to work on this. I think this would be a nice improvement. I am unsure how to overload the function typeinfo_implicit.

If I add something such as

function Base.typeinfo_implicit(x::Day)
    return true
end

Doing that seems to not do anything as calling Base.typeinfo_implicit(Day) just called the original function which is defined in arrayshow.jl

::Type{Day} should work.

OK, I'm on board with trying switching array elements to :compact 3-arg show. Triage can discuss once we have experimental results.

The other discussion item is what to show in Vectors. I'm inclined to just remove the inconsistency and use the same printing as matrices. Printing vector elements non-compactly is nice for showing all digits of numbers, but I'm not sure it's really so important or useful outside of that.

ok cool, this is my current working output. I'm thinking this satisfies all if not most of the discussion here. Hopefully if this makes people happy, I can move on to getting the other time/date types feeling consistent with this, and I'll open up a PR.

julia> using Dates
julia> d = Day(1)
1 day
julia> show(d)
Day(1)
julia> show([d])
[Day(1)]
julia> show([d d])
[Day(1) Day(1)]
julia> show(IOContext(stdout, :compact=>true), [d])
[Day(1)]
julia> show(IOContext(stdout, :compact=>false), [d])
[Day(1)]
julia> [d, d][1]
1 day

julia> fill(d, 2)
2-element Array{Day,1}:
 1 day
 1 day

julia> fill(d, 2, 2)
2×2 Array{Day,2}:
 1 day  1 day
 1 day  1 day

julia> print(d)
1 day
julia> string(d)
"1 day"

julia> repr(d)
"Day(1)"

julia> fill(d => d, 2)
2-element Array{Pair{Day,Day},1}:
 Day(1) => Day(1)
 Day(1) => Day(1)

julia> fill(d => d, 2, 3)
2×3 Array{Pair{Day,Day},2}:
 Day(1)=>Day(1)  Day(1)=>Day(1)  Day(1)=>Day(1)
 Day(1)=>Day(1)  Day(1)=>Day(1)  Day(1)=>Day(1)

Looks perfect to me!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sbromberger picture sbromberger  ·  3Comments

ararslan picture ararslan  ·  3Comments

Keno picture Keno  ·  3Comments

musm picture musm  ·  3Comments

i-apellaniz picture i-apellaniz  ·  3Comments