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


#1

I have my own type “cyclotomics” (sums of roots of unity) for which I have a fancy display which I want to see at the REPL. I want also to have a function which can output my numbers in a way which can be read back in (I assume this function should be repr but perhaps there is a better choice).

In other terms, I want to see at the REPL:

julia> l
4×4 Array{Cyc{Int64},2}:
    2        0             -ζ₃²             1+ζ₄
    0       -2          2ζ₃+ζ₃²            -1+ζ₄
 -ζ₃²  2ζ₃+ζ₃²              2ζ₃  ζ₁₂⁴-ζ₁₂⁷-ζ₁₂¹¹
 1+ζ₄    -1+ζ₄  ζ₁₂⁴-ζ₁₂⁷-ζ₁₂¹¹              2ζ₄

while

julia> repr(l)
"Cyc{Int64}[2 0 -E(3,2) 1+E(4); 0 -2 2E(3)+E(3,2) -1+E(4); -E(3,2) 2E(3)+E(3,2) 2E(3) E(12,4)-E(12,7)-E(12,11); 1+E(4) -1+E(4) E(12,4)-E(12,7)-E(12,11) 2E(4)]"

To be able to achieve this, I need to detect within show that I am somewhere deep below a call starting from the REPL. Is there a way? Or is my design wrong and there is another way to accomplish the same aim?


#2

I’m not exactly clear on why you need to know you are in the REPL, but…

you can define show specific to your datatype, i.e.

show(io::IO, Array{Cyc{T},2}) where T

(I think).
That will get you your REPL display function. Now assuming you need to know you are in the REPL, you should be able to use

Base.atreplinit — Function.

to set a global variable to indicate the REPL is active. I suspect that there must be something set in the global environment when the REPL is active but I wasn’t able to find one.

Which raises an interesting question, is it possible to list all of the global variables that currently defined in the REPL ?


#3

That does not answer my question. I do not need to know if the REPL is active, but that the display being computed is destined to the REPL or not. Even if I am under the REPL (the REPL is active), I still want my repr function to return a parsable string. I know that a call directly made by the REPL has the form
show(io,MIME::“text/plain”,x) but this call will in turn make other calls to display an array and I want to know
if I am in one of these other calls.


#4

I presume that you could look at the stack trace to figure out where it’s called from, but that sounds very hacky. Can’t you just override repr for your type to not call show? Or create a different function altogether, like serialize_to_string.


#5

If I create a function which does not exists like serialize_to_string I have to teach it how to serialize everything. I will explore if overriding repr for my type does the job. It would be nice if printing at the REPL
would set a property in the IOContext so one could know that its being done.


#6

That is trivial: have it forward to repr by default, and override it for your type. But then your better off just overriding repr in the first place.


#7

Your solutions do not work: repr calls internally show, not recursively repr


#8

? Yes it calls show, that’s why you need to override it to not call show. Could you paste some code so that we can see what you’re trying?


#9

I have a function format which computes the string I want

julia> Cycs.format(E(3),:fancy)
"ζ₃"

julia> Cycs.format(E(3),:plain)
"E(3)"

Then I tried the following:

Base.show(io::IO, p::Cyc)=print(io,format(p,:fancy))
Base.repr(p::Cyc)=format(p,:plain)

but repr for an array, for instance, will not call my repr


#10

Ah, I see. Then I’d try using the context keyword in repr. It gets forwarded to show. I think this should work recursively. Or create your own custom IO and pass to show.


#11

Overload show(io::IO, x::Cyc) for simple output (used in print, repr, etcetera), and overload show(io::IO, ::MIME"text/plain", x::Cyc) for more verbose output (used in REPL display) etcetera, as explained in the manual. Don’t overload repr, print, or similar.

However, note that output of arrays and other containers, even in the REPL, outputs individual elements using print for compact output. If you want fancier interactive output for Array{Cyc}, one option is to overload show(io::IO, ::MIME"text/plain", a::AbstractArray{<:Cyc}). For example, the Colors.jl package does this to get custom graphical output for an array of colors.


#12

Looking at the code for Colors.jl it seems to me that show(io::IO, ::MIME"text/plain", a::AbstractArray{<:Color}) does all the job of computing the display of the array. I do not want to do that,
I just want this function to do its usual job, but detect in show(io,x::Cyc) that I have been called by it.

I could do that if my overloaded version added a property to the IOcontext, then called the non-overloaded
version. Is there a way to overload a function but then call inside the overloading code the non-overloaded version?


#13

You can print pretty.(a). Or like this, to print the original type:

show(io::IO, ::MIME"text/plain", a::AbstractArray{<:Cyc}) = 
    Base.print_array(IOContext(io, :compact => true, :limit => true),  pretty.(a) )

Here Base.print_array is an internal function which changed name in 0.6 -> 0.7 so you may have to fix things if it changes again.


#14

Using repr and context:

struct Cyc
    s::String
end

Base.show(io::IO, c::Cyc) =
    print(io, get(io, :style, :plain) ≡ :fancy ? uppercase(c.s) : c.s)

format(c, style) = repr(c; context = :style => style)

Result:

julia> format(Cyc("foo"), :plain)
"foo"

julia> format(Cyc("foo"), :fancy)
"FOO"

julia> format([Cyc("foo") Cyc("bar")], :plain)
"Cyc[foo bar]"

julia> format([Cyc("foo") Cyc("bar")], :fancy)
"Cyc[FOO BAR]"

#15

Yes, with your solution one could have
serialize_to_string(c)= repr(c; context = :serialize => true)
But it seems to me from various comments that repr itself is intended to be this serialize_to_string.


#16

How does your solution exactly works?
When I try

Base.show(io::IO, ::MIME"text/plain", a::AbstractArray{<:Cyc}) = 
  Base.print_array(IOContext(io, :compact => true, :limit => true),
                   format.(a,:fancy) )

I get

julia> l
 "2"     "0"        "-ζ₃²"             "1+ζ₄"           
 "0"     "-2"       "2ζ₃+ζ₃²"          "-1+ζ₄"          
 "-ζ₃²"  "2ζ₃+ζ₃²"  "2ζ₃"              "ζ₁₂⁴-ζ₁₂⁷-ζ₁₂¹¹"
 "1+ζ₄"  "-1+ζ₄"    "ζ₁₂⁴-ζ₁₂⁷-ζ₁₂¹¹"  "2ζ₄"

#17

Well you can do something like this to have the array version forward to the object version:

struct Cyc
    s::String
end

Base.show(io::IO, c::Cyc) = print(io, c.s)
Base.show(io::IO, ::MIME"text/plain", c::Cyc) = print(io, uppercase(c.s))
Base.show(io::IO, ::MIME"text/plain", a::AbstractArray{<:Cyc}) =
    print(map(c -> repr(MIME("text/plain"), c), a))

With result:

julia> Cyc("foo")
FOO

julia> repr(Cyc("foo"))
"foo"

julia> [Cyc("foo") Cyc("bar")]
["FOO" "BAR"]

julia> repr([Cyc("foo") Cyc("bar")])
"Cyc[foo bar]"

However, I wouldn’t use this solution, since it will only work with arrays. If you have any other collections, such as tuples or dictionaries, it won’t work:

julia> (Cyc("foo"), Cyc("bar"))
(foo, bar)

julia> repr((Cyc("foo"), Cyc("bar")))
"(foo, bar)"

In contrast with my previous suggestion which supports any collection:

julia> format((Cyc("foo"), Cyc("bar")), :plain)
"(foo, bar)"

julia> format((Cyc("foo"), Cyc("bar")), :fancy)
"(FOO, BAR)"

julia> format(Dict(1 => Cyc("foo"), 2 => Cyc("bar")), :plain)
"Dict(2=>bar,1=>foo)"

julia> format(Dict(1 => Cyc("foo"), 2 => Cyc("bar")), :fancy)
"Dict(2=>BAR,1=>FOO)"

#18

It does not work either With my code:

Base.show(io::IO, c::Cyc) = print(io, format(c,:plain))
Base.show(io::IO, ::MIME"text/plain", c::Cyc) = print(io, format(c,:fancy))
Base.show(io::IO, ::MIME"text/plain", a::AbstractArray{<:Cyc}) =
    print(map(c -> repr(MIME("text/plain"), c), a))

gives

julia> l=[E(i)+E(j) for i in 1:4, j in 1:4]
["2" "0" "-ζ₃²" "1+ζ₄"; "0" "-2" "2ζ₃+ζ₃²" "-1+ζ₄"; "-ζ₃²" "2ζ₃+ζ₃²" "2ζ₃" "ζ₁₂⁴-ζ₁₂⁷-ζ₁₂¹¹"; "1+ζ₄" "-1+ζ₄" "ζ₁₂⁴-ζ₁₂⁷-ζ₁₂¹¹" "2ζ₄"]

#19

Then there’s the stack trace hack that I mentioned:

struct Cyc
    s::String
end

function Base.show(io::IO, c::Cyc)
    fancy = any(startswith.(string.(stacktrace()), "run_repl("))
    print(io, fancy ? uppercase(c.s) : c.s)
end

With result:

julia> Cyc("foo")
FOO

julia> repr(Cyc("foo"))
"foo"

julia> [Cyc("foo") Cyc("bar")]
1×2 Array{Cyc,2}:
 FOO  BAR

julia> repr([Cyc("foo") Cyc("bar")])
"Cyc[foo bar]"

Please don’t do this :slight_smile:


#20

Yes. Use invoke. For example:

function show(io::IO, m::MIME"text/plain", a::AbstractArray{<:Cyc,N}) where N
    invoke(show, Tuple{IO, MIME"text/plain", AbstractArray{<:Any,N}}, io, m, a)
end

where in the inner call you might replace io with some custom IOContext.