# Having human-readable and parseable display styles

**URL:** <https://discourse.julialang.org/t/having-human-readable-and-parseable-display-styles/92946>\
**Category:** Internals & Design\
**Tags:** repl, display, show\
**Created:** [January 14, 2023, 12:42am UTC](https://discourse.julialang.org/t/having-human-readable-and-parseable-display-styles/92946 "2023-01-14T00:42:15Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Jollywatt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jollywatt/32/202198_2.png) [@Jollywatt](https://discourse.julialang.org/u/Jollywatt)\
**Post date:** [January 14, 2023, 12:42am UTC](https://discourse.julialang.org/t/having-human-readable-and-parseable-display-styles/92946/1 "2023-01-14T00:42:16Z")

</div>

What’s the best way to have both human-readable and parseable display styles for your own type, when there are both multiline and inline versions of the human-readable style?

For example, suppose I have a vector-like type which should display its components in “unit vector notation”, (e.g., 2𝒗\_1 + 4𝒗\_3) like so:

```julia
julia> Foo([2, 0, 4])
3-component Foo{Int64}:
 2v1
 4v3

```

I want `repr` to be reflective (parseable), so that:

```julia
julia> repr(Foo([0, 10, 20]))
"Foo{Int64}([0, 10, 20])"

```

As I understand, array elements are displayed with `show(::IO, ::MIME"text/plain", …)`, unless the result contains multiple lines, in which case it falls back to `show(::IO, …)`. So by default, an array of `Foo`s would show elements in the `repr`-style above.

However, my `Foo` type also lends itself to human-readable single-line representation, which I’d like to use for displaying in arrays:

```julia
julia> [Foo([2, 0, 4]), Foo([50, 60, 0])]
2-element Vector{Foo{Int64}}:
 2v1 + 4v3
 50v1 + 60v2

```

What’s the preferred way to achieve these three behaviours at once? I.e., human-readable multi-line, human-readable single-line, and parseable (single-line) display styles.

I’ve been able to find a bit of a hack which (seems) to work by using the presence of the IO property `:typeinfo` to guess that printing is happening inside an array.

> **Hacky implementation**
>
> ```julia
> struct Foo{T}
> comps::Vector{T}
> end
> 
> function showcomps(io::IO, comps, inline=true, indent=1)
> isfirst = true
> inline && print(io, " "^indent)
> for (i, x) in enumerate(comps)
> iszero(x) && continue
> isfirst || print(io, inline ? " + " : "\n")
> print(io, " "^(indent*!inline), x, "v", i)
> isfirst = false
> end
> end
> 
> Base.show(io::IO, f::Foo) = print(io, typeof(f), "(", f.comps, ")")
> function Base.show(io::IO, ::MIME"text/plain", f::Foo)
> if :typeinfo ∈ keys(io)
> # assume showing as element within array
> showcomps(io, f.comps, true, 0)
> else
> # show in full
> println(io, length(f.comps), "-component ", typeof(f), ":")
> showcomps(io, f.comps, false)
> end
> end
> 
> ```

But I’m not sure if this is the **preferred** way. In particular, other array types (like `DataFrame`) would use the `repr` version, even when displaying in a human-readable format.

I would appreciate any extra insight into the print/show/display interface!

---

<div class="post-metadata">

**Author:** ![Jean\_Michel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jean_michel/32/8282_2.png) [@Jean\_Michel](https://discourse.julialang.org/u/Jean_Michel)\
**Post date:** [January 14, 2023, 11:32am UTC](https://discourse.julialang.org/t/having-human-readable-and-parseable-display-styles/92946/2 "2023-01-14T11:32:07Z")

</div>

I also have been wondering about this question. Some programs use the `:typeinfo` io property for a  
human-readable `show ` and others use the `:limit` io property (`:limit` is true within the REPL)

---

<div class="post-metadata">

**Author:** ![Jollywatt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jollywatt/32/202198_2.png) [@Jollywatt](https://discourse.julialang.org/u/Jollywatt)\
**Post date:** [January 15, 2023, 8:28am UTC](https://discourse.julialang.org/t/having-human-readable-and-parseable-display-styles/92946/3 "2023-01-15T08:28:38Z")

</div>

I don’t have a proper solution to this yet, but in the meantime, I’m using `keys(io)` as a litmus test as follows:

```julia
function Base.show(io::IO, f::Foo)
	if : __PRETTY_TABLES_DATA__ ∈ keys(io) # shown in a pretty table/DataFrame
		# compact/single-line human-readable style
	else
		# parseable style
	end
end
function Base.show(io::IO, ::MIME"text/plain", f::Foo)
	if :typeinfo ∈ keys(io) # shown in an Array
		# compact/single-line human-readable style
	else
		# full/multi-line human-readable style
	end
end

```

This displays `Foo`s using parseable style when occuring in `Tuple`s or `Pair`s, etc, (which are themselves parseable), but uses compact human-readable style for `Array`s and `DataTable`s (which aren’t parseable when displayed).

However, new human-readable container types would have to be treated on a case-by-case basis…

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [January 15, 2023, 8:51am UTC](https://discourse.julialang.org/t/having-human-readable-and-parseable-display-styles/92946/4 "2023-01-15T08:51:54Z")

</div>

No insights to offer on the `show` interface, but some thoughts…

1. The representation 2 v\_1 + 4v\_3 is not in a typical “unit vector” notation; conventionally, the unit vectors are represented (for \mathbb R^3 space) \hat e\_1=(1, 0, 0), \hat e\_2 = (0, 1, 0), and \hat e\_3 = (0, 0, 1), and then a vector \vec v=(2, 0, 4) might be represented \vec v = 2\hat e\_1 + 4\hat e\_3. Some people might write the components as \vec v = (v\_1, v\_2, v\_3), so that \vec v = v\_1 \hat e\_1 + v\_2 \hat e\_2 + v\_3 \hat e\_3.

2. Why not show the zero elements? In the grander scheme, they probably improve legibility.

3. Can you make human-readable form converge with reflective form? maybe something like this:

```julia
julia> Foo([2, 0, 4])
3-element Foo{Int64}:
 2e[1]
 0e[2]
 4e[3]

julia> [Foo([2, 0, 4]), Foo([50, 60, 0])]
2-element Vector{Foo{Int64}}:
 2e[1] + 0e[2] + 4e[3]
 50e[1] + 60e[2] + 0e[3]

julia> [2e[1] + 0e[2] + 4e[3], 50e[1] + 60e[2] + 0e[3]]
2-element Vector{Foo{Int64}}:
 2e[1] + 0e[2] + 4e[3]
 50e[1] + 60e[2] + 0e[3]

```

This might be achieved through something like this:

> **Some code**
>
> ```julia
> struct Component{i, T<:Real}
> comp::T    
> Component(i::Int) = new{i, Bool}(true)
> Component(i::Int, comp::T) where T = new{i, T}(comp) 
> end
> Base.show(io::IO, c::Component{i}) where i = print(io, "$(c.comp)e[$i]")
> Base.show(io::IO, c::Component{i,Bool}) where i = c.comp ? print(io, "e[$i]") : print(io, "Component($i, false)")
> direction(c::Component{i}) where i = i::Int
> abs(c::Component) = c.comp
> function e end
> Base.getindex(::typeof(e), i::Int) = Component(i)
> Base.:*(x::Number, c::Component{i}) where i = Component(i, x*c.comp)
> struct Foo{T} comps::Vector{T} end
> Foo(cs::Component...) = begin
> all(x ∈ map(direction, cs) for x=1:length(cs)) || error("Foo not fully specified")
> v = Vector{mapreduce(typeof ∘ abs, promote_type, cs)}(undef, length(cs))
> for c ∈ cs; v[direction(c)] = abs(c) end
> Foo(v)
> end
> Base.show(io::IO, ::MIME"text/plain", f::Foo{T}) where T = 
> print(io, "$(length(f.comps))-element Foo{$T}:\n"*join(map(((i,v),)->" $(v)e[$i]", enumerate(f.comps)), "\n"))
> Base.show(io::IO, f::Foo) = print(io, join(map(((i,v),)->"$(v)e[$i]", enumerate(f.comps)), " + "))
> Base.promote_rule(::Type{Foo{T1}}, ::Type{Foo{T2}}) where {T1,T2} = Foo{promote_type(T1, T2)}
> Base.convert(::Type{Foo{T}}, f::Foo) where T = Foo(T[f.comps...])
> Base.typejoin(::Type{Foo{T1}}, ::Type{Foo{T2}}) where {T1,T2} = Foo{var"#T"} where {var"#T"<:typejoin(T1, T2)}
> Base.:+(cs::Component...) = Foo(cs...)
> 
> ```

It’s sort of a yak-shaving contest, but it was fun to write. Also possible to programmatically generate and export constants `ê₁`, `ê₂`, `ê₃`, …, `ê₁₀₀₀₀`. as stand-ins for `e[1]`, `e[2]`, `e[3]`, …, `e[10_000]`—that would make things more readable.

All that said, I’m kinda attracted to the simple approach:

```julia
struct Foo{T} comps::Vector{T} end
Foo(vals::Number...) = Foo([vals...])
Foo{T}(vals::Number...) where T = Foo{T}([vals...])
Base.show(io::IO, f::Foo{T}) where T =
    print(io, "Foo"*(isconcretetype(T) ? "" : "{$T}")*"""($(join(f.comps, ", ")))""")
Base.promote_rule(::Type{Foo{T1}}, ::Type{Foo{T2}}) where {T1,T2} = Foo{promote_type(T1, T2)}
Base.convert(::Type{Foo{T}}, f::Foo) where T = Foo(T[f.comps...])
Base.typejoin(::Type{Foo{T1}}, ::Type{Foo{T2}}) where {T1,T2} = Foo{var"#T"} where {var"#T"<:typejoin(T1, T2)}

```

resulting in:

```julia
julia> Foo(2, 0, 4)
Foo(2, 0, 4)

julia> [Foo(2, 0, 4), Foo(50, 60, 0)]
2-element Vector{Foo{Int64}}:
 Foo(2, 0, 4)
 Foo(50, 60, 0)

```

---

<div class="post-metadata">

**Author:** ![Jollywatt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jollywatt/32/202198_2.png) [@Jollywatt](https://discourse.julialang.org/u/Jollywatt)\
**Post date:** [January 15, 2023, 9:23am UTC](https://discourse.julialang.org/t/having-human-readable-and-parseable-display-styles/92946/5 "2023-01-15T09:23:33Z")

</div>

Haha, these are certainly lots of interesting thoughts!

For context, this is for a [multivector](https://en.wikipedia.org/wiki/Multivector) type in the (unregistered) package [Jollywatt/GeometricAlgebra.jl](https://github.com/Jollywatt/GeometricAlgebra.jl).

1. The reason for diverging from (x, y, z)-style notation is because bivectors and general multivectors don’t translate well. E.g., how could you write 𝒆\_1𝒆\_2 + 42?

2. General multivectors can have up to 2^n components in n dimensions, so omitting zeros is handy. By default, I omit zeros for inhomogeneous multivectors only:

3. This is a good idea. Having the human-readable version be parseable, so that 𝒆\_1𝒆\_2 + 42 is shown as `v[1]v[2] + 42`. However, to achieve true reflectivity, I need the missing type information (the algebra/dimension, the underlying array type, etc). It’s also dodgy assuming `v` is always in scope, especially because the basis blade style is customisable — e.g., it could be printed as `𝐯₁∧𝐯₂ + 42`. So I think it best to have

---

<div class="post-metadata">

**Author:** ![Jean\_Michel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jean_michel/32/8282_2.png) [@Jean\_Michel](https://discourse.julialang.org/u/Jean_Michel)\
**Post date:** [January 15, 2023, 5:03pm UTC](https://discourse.julialang.org/t/having-human-readable-and-parseable-display-styles/92946/6 "2023-01-15T17:03:43Z")

</div>

Just a remark. You don’t have to `print(repr(x))`, just `print(x)` does that.
