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

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)
  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

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

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

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)
        print(io, "MyType")
        print(io, "\n  ")
        print(io, "some_number: $(obj.some_number)")
        print(io, "\n  ")
        print(io, "some_dict: $(obj.some_dict)")

This works fine:

julia> t = MyType(5.0, Dict(:a => 1, :b => 2))
  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?


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