JuliaML / LearnBase.jl

Abstractions for Julia Machine Learning Packages
Other
17 stars 10 forks source link

Properly display derived types of `AbstractSet` in REPL or IJulia #40

Open ShuhuaGao opened 4 years ago

ShuhuaGao commented 4 years ago

I encountered the issue when playing with Reinforce.jl, and the essential reason is in LearnBase.

Issue description: When a concrete type in LearnBase derived from AbstractSet is displayed automatically in REPL or IJulia (i.e., with no semicolon at the end), an error will be caused like follows:

Error showing value of type LearnBase.DiscreteSet{Array{Int64,1}}:
ERROR: MethodError: no method matching iterate(::LearnBase.DiscreteSet{Array{Int64,1}})
...(a lot more, omitted here)

How to reproduce

julia> using LearnBase
julia> ds = LearnBase.DiscreteSet([1, 2, 3])

Note that, if you suppress the output with a semicolon and then print it manually with print(ds), then no error happens and the printed result is LearnBase.DiscreteSet{Array{Int64,1}}([1, 2, 3]).

Reason of the error The reason is that when a variable is displayed automatically in REPL or IJulia, the display function is used. That is, if you print the output with display(ds), the same error is induced. It seems that, for subtypes of AbstractSet, the default display method tries to iterate over each element. However, there is no default implementation in Julia to iterate an AbstractSet. (see documentation)

Possible fix Two obvious fixes are possible

  1. Add Base.iterate method for each related type in LearnBase. Example: if we dispatch Base.iterate for DiscreteSet by iterating DiscreteSet.items, the displayed output of the above ds is

    LearnBase.DiscreteSet{Array{Int64,1}} with 3 elements:
    1
    2
    3

    However, an iteration method may make little sense for LearnBase.IntervalSet.

  2. Support display by implementing the MIME show method for relevant types. (see documentation) Example:

    Base.show(io::IO, ::MIME"text/plain", set::LearnBase.IntervalSet) = print(io, "$(typeof(set)):\n  ", "lo = $(set.lo)\n  ", "hi = $(set.hi)\n")

    will display a LearnBase.IntervalSet(-1.0, 1.0) as

    LearnBase.IntervalSet{Float64}:
    lo = -1.0
    hi = 1.0

My suggestion is that

I can make a PR if you think the above suggestion is reasonable.

juliohm commented 4 years ago

Can you please point out in the codebase where you think display is being defined? It is strange that you are getting a print issue when we don't define any show method in the codebase, as far as I remember.

ShuhuaGao commented 4 years ago

Can you please point out in the codebase where you think display is being defined? It is strange that you are getting a print issue when we don't define any show method in the codebase, as far as I remember.

Indeed no show method is defined. That is why the error is caused. Could you reproduce the error mentioned above?

Given an object x, Julia provides a default and proper implementation for show(x) and thus print(x). However, REPL and IJulia outputs an object with display(x) by default (i.e., with neither manual call of print etc. nor semicolon termination) For text output (which is true here), display(x) falls back to show(stdout, "text/plain", x) (see doc here).

Since no method show(stdout, "text/plain", x::LearnBase.IntervalSet) has been defined, the default dispatch of Julia seems to not work well and leads to the above errors. According to the error information, the default dispatch tries to call iterate on LearnBase.IntervalSet. The call of iterate is attributed to the subtyping of AbstractSet. In Julia codebase, there is (source)

function show(io::IO, ::MIME"text/plain", t::AbstractSet{T}) where T

The object t will be iterated inside the above function. Unfortunately, the Base.iterate method is not provided for LearnBase.DiscreteSet (and, e.g., LearnBase.IntervalSet). The key is that Julia provides no default implementation of Base.iterate for AbstractSet.

Overall, the issue can be easily fixed by providing show(stdout, "text/plain", x::LearnBase.DiscreteSet) or dispatching Base.iterate for DiscreteSet (and other similar types).

juliohm commented 4 years ago

I can reproduce the issue, thanks for reporting. Do you mind submitting a PR with the show methods implemented? I am not following the AbstractSet concept in Base, nor these set types defined in LearnBase. It would also be nice to know where they are being used other than Reinforce.jl. We need to update LearnBase and continue with the clean up. I've been trying to find the time to work on it, but the GeoStats.jl stack is really consuming a lot of my time.