User-defined Show Method in Julia

User-defined Show Method in Julia

How do you best configure the display of custom type contents?

I often find myself looking for a way to write custom display methods for Julia types on the REPL. Time to write it down in a short pragmatic blog post, for you and my future self.

What's the issue? When exploring on the Julia REPL or in notebooks, you display your own custom type, then it doesn't look always look the most informative. Let's say you have some type:

struct MyType
    some_number::Float64
    some_dict::Dict
end

You can quickly make an object and display it.

julia> obj = MyType(4.0, Dict(:x => 5))
MyType(4.0, Dict(:x => 5))

Okay... Julia basically shows the constructor of the object. I would like to see the field names, or maybe other information. Sometimes I want to see statistical properties for example, instead of the raw data.

As an alternative, to quickly see the field names, you can dump the content of an object. Which is nice for simple objects, but I explicitly put a Dict in there, because it'll dump the dictionary internals, which you don't want to see:

julia> dump(obj)
MyType
  some_number: Float64 4.0
  some_dict: Dict{Symbol, Int64}
    slots: Array{UInt8}((16,)) UInt8[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82]
    keys: Array{Symbol}((16,))
      1: #undef
      2: #undef
      ...
      15: #undef
      16: Symbol x
    vals: Array{Int64}((16,)) [5065505441550857052, 465637893754, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5]
    ndel: Int64 0
    count: Int64 1
    age: UInt64 0x0000000000000001
    idxfloor: Int64 16
    maxprobe: Int64 0

Not pretty. How to improve this developer experience?

Compact Mode

Before I go to the solution, it turns out there is a "compact" mode to displaying objects. You can notice this behavior when you place dictionaries inside an array for example:

julia> d = Dict(:a => 1, :b => 2, :c => 3)
Dict{Symbol, Int64} with 3 entries:
  :a => 1
  :b => 2
  :c => 3

julia> [d, Dict(:d => 4)]
2-element Vector{Dict{Symbol, Int64}}:
 Dict(:a => 1, :b => 2, :c => 3)
 Dict(:d => 4)

You see that the dictionary is displayed differently in the two cases above. Inside the array we prefer a more compact display, since you may have many objects. I often forget about this compact mode, and then I get ugly array printing.

Here's some discussion on the topic on the Julia discourse.

Custom show

In the end, this is a typical approach I take. You can make it a lot more fancy if you like, but this is a good starting point:

struct MyType
    some_number::Float64
    some_dict::Dict
end

# default show used by Array show
function Base.show(io::IO, obj::MyType)
    compact = get(io, :compact, true)
    print_object(io, obj, compact)
end

# default show used by display() on the REPL
function Base.show(io::IO, mime::MIME"text/plain", obj::MyType)
    compact = get(io, :compact, false)
    print_object(io, obj, compact)
end

function print_object(io::IO, obj::MyType, compact::Bool)
    if compact
        # write something short, or go back to default mode
        Base.show_default(io, obj)
    else
        print(io, "MyType")
        print(io, "\n  ")
        print(io, "some_number: $(obj.some_number)")
        print(io, "\n  ")
        print(io, "some_dict: $(obj.some_dict)")
    end
end

This works fine:

julia> t = MyType(5.0, Dict(:a => 1, :b => 2))
MyType
  some_number: 5.0
  some_dict: Dict(:a => 1, :b => 2)

julia> [t]
1-element Vector{MyType}:
 MyType(5.0, Dict(:a => 1, :b => 2))

You can make your type printing as fancy as you desire.

One additional trick, to make the code more concise when you have a lot of properties with special types, you can also loop over the propertynames(obj) and use for example getproperty(obj, :name) . Now I hardcoded the property names.

Here's where I found stuff in the base language:

If you read the code, please note the other IO options, like :limit. Can you guess what it's used for?

Conclusion

Well that's it, hope it helps as a reference to you and future me ;)