How to detect in `show` that you are computing display for the REPL?

Thanks, but I tried:

function Base.show(io::IO, m::MIME"text/plain", a::AbstractArray{<:Cyc,N}) where N
    invoke(show, Tuple{IO, MIME"text/plain", AbstractArray{Any,N}}, 
           IOContext(io, :repl=>true), m, a)
end

Base.show(io::IO, c::Cyc) = print(io,format(c,
                 get(io, :repl=>true, false) ? :fancy : :plain))

and got

julia> l=[E(i)+E(j) for i in 1:4, j in 1:4]
Error showing value of type Array{Cyc{Int64},2}:
ERROR: invoke: argument type error
Stacktrace:
 [1] show(::IOContext{REPL.Terminals.TTYTerminal}, ::MIME{Symbol("text/plain")}, ::Array{Cyc{Int64},2}) at /home/jmichel/julia/Gapjm/src/Cycs.jl:268
 [2] display(::REPL.REPLDisplay, ::MIME{Symbol("text/plain")}, ::Any) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.0/REPL/src/REPL.jl:131
 [3] display(::REPL.REPLDisplay, ::Any) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.0/REPL/src/REPL.jl:135
 [4] display(::Any) at ./multimedia.jl:287
 [5] #invokelatest#1 at ./essentials.jl:697 [inlined]
 [6] invokelatest at ./essentials.jl:696 [inlined]
 [7] print_response(::IO, ::Any, ::Any, ::Bool, ::Bool, ::Any) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.0/REPL/src/REPL.jl:154
 [8] print_response(::REPL.AbstractREPL, ::Any, ::Any, ::Bool, ::Bool) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.0/REPL/src/REPL.jl:139
........

(it goes on 20 stack frames)

Sorry, it should be AbstractArray{<:Any,N}; I fixed it in my post above.

Thanks. I marked your reply as giving the answer. However, as Bennedich noted, this solves one of the cases of REPL display. One would have to do the same overloading job with other data structures containing Cycs, for instance:

julia> a=Dict(1=>E(3),2=>E(4))
Dict{Int64,Cyc{Int64}} with 2 entries:
  2 => E(4)
  1 => E(3)

where I would like to see:

julia> a=Dict(1=>E(3),2=>E(4))
Dict{Int64,Cyc{Int64}} with 2 entries:
  2 => ζ₄
  1 => ζ₃

It would be nice if whenever printing at the repl, the IOContext would include :repl=>true

If you want repr to create parsable output, and want this to work with any container type, I would still consider overloading repr and use the context keyword we discussed earlier. It seems to me that this solves all of your problems?

The solution above seems far from optimal since you’ll need to add new code for every Cyc container type you’ll be using, so in a software engineering sense you are introducing technical debt (not solving the problem once and for all).

Sample code:

struct Cyc n::Int end

E(n) = Cyc(n)

format(c, style) = style == :plain ? "E($(c.n))" : "ζ$(c.n)"

Base.show(io::IO, c::Cyc) = print(io, format(c, get(io, :style, :fancy)))
Base.repr(x) = repr(x; context = :style => :plain)

Testing it:

julia> c = E(4)
ζ4

julia> v = [E(i+j) for i in 1:4, j in 1:4]
4×4 Array{Cyc,2}:
 ζ2  ζ3  ζ4  ζ5
 ζ3  ζ4  ζ5  ζ6
 ζ4  ζ5  ζ6  ζ7
 ζ5  ζ6  ζ7  ζ8

julia> d = Dict(1=>E(3), 2=>E(4))
Dict{Int64,Cyc} with 2 entries:
  2 => ζ4
  1 => ζ3

julia> println("My cyc is $c and my dict is $d")
My cyc is ζ4 and my dict is Dict(2=>ζ4,1=>ζ3)

julia> repr(c)
"E(4)"

julia> repr(v)
"Cyc[E(2) E(3) E(4) E(5); E(3) E(4) E(5) E(6); E(4) E(5) E(6) E(7); E(5) E(6) E(7) E(8)]"

julia> repr(d)
"Dict(2=>E(4),1=>E(3))"

Thanks. Maybe it was not clear to you, but I understood quite well your solution before and saw that it works.
The reason I went on is to explore what well-informed people like stevengj would say about the language design.

It seems my problem is a quite general one, and I would like to see what solution would be considered “the right way” by the language designers. I was told "do not overload repr". Your proposal is a particular way
to build a “serialize_to_string” function. Now why does this function does not yet exist? Is it not because
people think that repr (without an additional argument) should be this function?

My self, I think my proposal (make so that any output call from the REPL adds a marker to the IOContext)
could be a good design. I hope that if any other commiters visits this thread, they would give their opinion.

Got it. Good questions. If there’s no better alternative, I am also curious how “overload every possible container of your type” would be preferred over “do not overload repr” in your case.

FYI, following IOContext are provided to show for the case the object in question is in a container (see below for the code to check it):

:typeinfo :limit :compact :color
display([T()]) T true true
show([T()]) T true true
repr([T()]) T true
display(Dict(:a=>T())) true true true
show(Dict(:a=>T())) T true true
repr(Dict(:a=>T())) T true

From this, it seems that differentiating display (i.e., REPL) and repr can be done by simply looking at :limit in show(::IO, ::T). See also: I/O and Network · The Julia Language

I also add that the Style Guide seems to explicitly discourage AbstractArray{<:Cyc} solution:

Don’t overload methods of base container types

It is possible to write definitions like the following:

show(io::IO, v::Vector{MyType}) = ...

This would provide custom showing of vectors with a specific new element type. While tempting, this should be avoided. The trouble is that users will expect a well-known type like Vector() to behave in a certain way, and overly customizing its behavior can make it harder to work with.

Style Guide · The Julia Language

…although it does not quite apply to your case, since you just use the container’s show almost as-is.


Aren’t you just need to check if repr or not, rather than REPL-or-not? If so, I think an alternative approach would be to add a public API show(::IO, ::MIME"application/julia", ::MyType) which is called from repr if it exists. (I know this MIME is not super legit but julia actually already “knows” it.)


Code to check the table
julia> struct LogIO end

julia> logio_log = []
0-element Array{Any,1}

julia> function logio_show(io, mime)
           push!(logio_log, ("mime", mime))
           push!(logio_log, ("io isa IOContext", io isa IOContext))
           if io isa IOContext
               push!(logio_log, ("io.dict", io.dict))
           end
           print(io, "LogIO")
       end
logio_show (generic function with 1 method)

julia> Base.show(io::IO, mime::MIME"text/plain", ::LogIO) =
           logio_show(io, mime)

julia> Base.show(io::IO, ::LogIO) =
           logio_show(io, nothing)

julia> empty!(logio_log)
       display([LogIO()])
       logio_log
1-element Array{LogIO,1}:
 LogIO
9-element Array{Any,1}:
 ("mime", nothing)
 ("io isa IOContext", true)
 ("io.dict", Base.ImmutableDict{Symbol,Any}(:typeinfo=>LogIO,:module=>Main,:limit=>true,:color=>true))
 ("mime", nothing)
 ("io isa IOContext", true)
 ("io.dict", Base.ImmutableDict{Symbol,Any}(:typeinfo=>LogIO,:module=>Main,:limit=>true,:color=>true))
 ("mime", nothing)
 ("io isa IOContext", true)
 ("io.dict", Base.ImmutableDict{Symbol,Any}(:typeinfo=>LogIO,:module=>Main,:limit=>true,:color=>true))

julia> empty!(logio_log)
       show([LogIO()])
       logio_log
LogIO[LogIO]3-element Array{Any,1}:
 ("mime", nothing)                                                                                                
 ("io isa IOContext", true)                                                                                       
 ("io.dict", Base.ImmutableDict{Symbol,Any}(:SHOWN_SET=>LogIO[LogIO],:compact=>true,:typeinfo=>LogIO,:color=>true))

julia> empty!(logio_log)
       repr([LogIO()])
       logio_log
3-element Array{Any,1}:
 ("mime", nothing)
 ("io isa IOContext", true)
 ("io.dict", Base.ImmutableDict{Symbol,Any}(:SHOWN_SET=>LogIO[LogIO],:compact=>true,:typeinfo=>LogIO))

julia> empty!(logio_log)
       display(Dict(:a=>LogIO()))
       logio_log
Dict{Symbol,LogIO} with 1 entry:
  :a => LogIO
3-element Array{Any,1}:
 ("mime", nothing)                                                                                                
 ("io isa IOContext", true)                                                                                       
 ("io.dict", Base.ImmutableDict{Symbol,Any}(:compact=>true,:SHOWN_SET=>Dict(:a=>LogIO),:module=>Main,:limit=>true,:color=>true))

julia> empty!(logio_log)
       show(Dict(:a=>LogIO()))
       logio_log
Dict(:a=>LogIO)3-element Array{Any,1}:
 ("mime", nothing)                                                                                                
 ("io isa IOContext", true)                                                                                       
 ("io.dict", Base.ImmutableDict{Symbol,Any}(:typeinfo=>LogIO,:compact=>true,:compact=>true,:typeinfo=>Pair{Symbol,LogIO},:SHOWN_SET=>Dict(:a=>LogIO),:color=>true))

julia> empty!(logio_log)
       repr(Dict(:a=>LogIO()))
       logio_log
3-element Array{Any,1}:
 ("mime", nothing)                                                                                                
 ("io isa IOContext", true)                                                                                       
 ("io.dict", Base.ImmutableDict{Symbol,Any}(:typeinfo=>LogIO,:compact=>true,:compact=>true,:typeinfo=>Pair{Symbol,LogIO},:SHOWN_SET=>Dict(:a=>LogIO)))

Thank you very much for your analysis. It seems that checking for ‘:limit’ is a perfectly practical solution now. Of course, the question is if it works by design or by chance; in this last case a more stable API would be useful. As you say, the question is distinguishing repr from other environments where the formatting is (presumably) for display purposes. It would be good to adopt and document a solution. Another formatting
environment for which a marker should be documented/agreed upon is building strings for TeXing.

:limit is used to truncate long output, so this implementation means to use fancy output whenever output is truncated. If the goal is to distinguish repr from other forms of display, this would not fully work, since for example print output and exceptions would still use repr style output.

I agree that using :limit is not fully satisfactory. But it kind of makes sense because :limit => true means you need to limit output and give up conveying all the information contents for the sake of simplicity. In this case, using unicode to “compress” information sounds reasonable.

I would say (as the person who introduced IOContext), that it was by-design and indicates whether the output is for interactive-consumption (and thus may truncate or eliminate information) or offline analysis (more machine parsable, avoiding discarding information).

4 Likes