Show and showcompact on custom types

I am trying to define a custom type with convenient methods for readout, and having some very basic trouble. I have read the documentation for show with no luck in sorting this out. Defining a method for Base.show(::IO, ::MyType) is simple enough, but when MyType is part of an array, the printout is repeated several times per instance in the array:

struct MyType
  x
end

function Base.show(io::IO, m::MyType) 
  println("instance of MyType:")
  println("x = ", m.x)
end

m = MyType(10)
M = [m, m]

At the console:


julia> m
instance of MyType:
x = 10


julia> M
2-element Array{MyType,1}:
instance of MyType:
x = 10
instance of MyType:
x = 10
 instance of MyType:
x = 10
instance of MyType:
x = 10

 instance of MyType:
x = 10
instance of MyType:
x = 10

If anyone knows the reason for this strange output, any help would be much appreciated!

One suspicion of mine is that this is somehow the result of showcompat, about which the documentation says: “a custom type should test get(io, :compact, false) in its normal show method”. However, I have not been able to find any examples of this in practice, and am not entirely sure what is meant by “test”.

Regardless of whether it is relevant to the problem I am experiencing, I would also like to define and use a showcompact method for some custom types to print nicely in arrays, and so far have not been able to find an example of that anywhere, nor another discussion topic mentioning this.

So if anyone knows the answer to these, or can point me in the right direction, I would be much obliged!

3 Likes

Eg see this example in Base, for Complex.

3 Likes

Thank you!! This is exactly what I was looking for. I have looked through some of the Base code in the past but never come across that.

The result:

function Base.show(io::IO, m::MyType)
    compact = get(io, :compact, false)

    if !compact
        println("Instance of MyType:")
        print("x = ")
    end
        show(io, m.x)
end

julia> m = MyType(10)
Instance of MyType:
x = 10

julia> [m,m]
2-element Array{MyType,1}:
 10
 10
1 Like

Normally, for multi-line display like this, you don’t use the compact attribute. You define a three-argument show method for text/plain. See the manual section on custom pretty-printing: https://docs.julialang.org/en/stable/manual/types/#Custom-pretty-printing-1

Thanks for the reply @stevengj. I have seen that section of the docs before, but trying to implement it in the past did not produce the results I was expecting:

struct MyType
    x
    y
end


function Base.show(io::IO, m::MyType)
    compact = get(io, :compact, false)

    x = m.x
    y = m.y

    if compact
        print(x, "($y)")
    else
        print(x, " ± $y")
    end
    
end

function Base.show(io::IO, ::MIME"text/plain", m::MyType)
 
    println("Examplary instance of MyType")
    show(io, m)
    
end
julia> m
Examplary instance of MyType
10 ± 21

julia> [m, m]
2-element Array{MyType,1}:
10(21)10(21) 10(21)10(21)
 10(21)10(21)

Maybe you can shed some light as to why the array output is so garbled? I was quite surprised actually that using if !compact in the previous method gave me the results I was expecting, but it seemed to work. I’m probably missing something obvious here…

Your show functions need to pass io to print and println. Otherwise you are printing to STDOUT, not to the specified io stream.

2 Likes

Of course! That seems to have worked now. Thanks!

In case anyone stumbles on this with the same problem:

function Base.show(io::IO, m::MyType)
    compact = get(io, :compact, false)

    x = m.x
    y = m.y

    if compact
        print(io, x, "($y)")
    else
        print(io, x, " ± $y")
    end
    
end

function Base.show(io::IO, ::MIME"text/plain", m::MyType)
    println("Examplary instance of MyType")
    show(io, m)
end
julia> m = MyType(10, 11)
Examplary instance of MyType
10 ± 11

julia> [m,m]
2-element Array{MyType,1}:
 10(11)
 10(11)
3 Likes

This is still missing an io argument.

It seems like it would be easier to do

Base.show(io::IO, ::MIME"text/plain", m::MyType) = print(io, "Examplary instance of MyType\n", m.x, " ± ", m.y)
Base.show(io::IO, m::MyType) = print(io, m.x, '(', m.y, ')')
6 Likes

I see. So there’s no reason to explicitly test for compact at all if you do it this way. Thanks for the correction.

I am writing a package where I sometimes want to display a custom type object in a markdown friendly format. In particluar, I want to record a summary of the object’s state to a .md file. For example, the object is a dictionary, and I want to display (or write it to file) as a markdown table, rather than in the usual way. Which variant of show should I be overloading to this?

Base.show(io::IO, ::MIME"text/markdown", m::MyType)

2 Likes

Thanks :slight_smile:

Sorry for reviving this but I think it’s worth to add here that the MIME method is not called if there is no newline character in the string passed to print. This is new to me, although I have written hundreds of custom show methods over the past years :laughh

I am using the example above where you can see that it works (read: does what I/we think) when a newline is involved, but if Base.show(io::IO, ::MIME"text/plain", m::MyType) passes a single line string to print, the “short show” (show(io.IO, m::MyType)) will not be called, at least not on the REPL, when showing instances:

julia> struct MyType
         x
         y
       end

julia> MyType("hello", 42)
MyType("hello", 42)

julia> Base.show(io::IO, ::MIME"text/plain", m::MyType) = print(io, "Examplary instance of MyType\n", m.x, " ± ", m.y)

julia> Base.show(io::IO, m::MyType) = print(io, m.x, '(', m.y, ')')

julia> MyType("hello", 42)
Examplary instance of MyType
hello ± 42

julia> [MyType("hello", 42), MyType("hello", 23), MyType("hello", 1/137)]
3-element Vector{MyType}:
 hello(42)
 hello(23)
 hello(0.0072992700729927005)

julia> Base.show(io::IO, ::MIME"text/plain", m::MyType) = print(io, "Examplary instance of MyType: ", m.x, " ± ", m.y)

julia> MyType("hello", 42)
Examplary instance of MyType: hello ± 42

julia> [MyType("hello", 42), MyType("hello", 23), MyType("hello", 1/137)]
3-element Vector{MyType}:
 Examplary instance of MyType: hello ± 42
 Examplary instance of MyType: hello ± 23
 Examplary instance of MyType: hello ± 0.0072992700729927005

I am now playing around with the :compact and :multiline attributes of IOContext but I am still a little bit confused :wink:

1 Like

This surprised me as well, but apparently this is the intentional as discussed in

2 Likes

Yes, this explains a few head scratches in the past :laughing:

1 Like

Rather than forcing yourself to include a newline, a better workaround to use 2-arg show when printing instances in a container is to look at the compact flag:

if get(io, :compact, false)
    show(io, x)
else
    # ... verbose show
end

in your 3-argument show.

As I commented on the linked issue above, I dislike this change to the printing architecture, which was hacked in to modify Date printing. I wish we could revert it, but maybe I’m in the minority.

2 Likes

Thanks @stevengj, indeed I am now using the :compact “hack”. I hope that one is stable.

I fully agree it should be changed back.

1 Like

This does not work, since :compact is not always set when containers are printed. For example, printing of Vector{MyType} does not set :compact => true.

Unless one looks at the stacktrace, there is currently no legal/safe/sane way to know if the printing of an element is called from the printing of a container.