Repository: JuliaAI/MLJBase.jl (julia)

Issue:
`show` for nodes broken in Julia 1.12
```julia
julia> X, _ = make_regression()
julia> X = source(X)
julia> mach = machine(Standardizer(), X)
julia> transform(mach, X)
Node @446 → Standardizer(…)
  args:
    1:	Source @209
  formula:
    Error showing value of type Node{Machine{Standardizer, Standardizer, true}, typeof(transform)}:

SYSTEM (REPL): showing an error caused an error
ERROR: 1-element ExceptionStack:
FieldError: type Core.TypeName has no field `mt`, available fields: `name`, `module`, `singletonname`, `names`, `atomicfields`, `constfields`, `wrapper`, `Typeofwrapper`, `cache`, `linearcache`, `partial`, `hash`, `max_args`, `n_uninitialized`, `flags`, `cache_entry_count`, `max_methods`, `constprop_heuristic`
Stacktrace:
  [1] getproperty(x::Core.TypeName, f::Symbol)
    @ Base ./Base_compiler.jl:54
  [2] _formula(stream::IOContext{REPL.LimitIO{…}}, X::Node{Machine{…}, typeof(transform)}, depth::Int64, indent::Int64)
    @ MLJBase ~/.julia/packages/MLJBase/F8Zzu/src/composition/learning_networks/nodes.jl:273
  [3] _formula(stream::IOContext{REPL.LimitIO{…}}, X::Node{Machine{…}, typeof(transform)}, indent::Int64)
    @ MLJBase ~/.julia/packages/MLJBase/F8Zzu/src/composition/learning_networks/nodes.jl:270
  [4] show(io::IOContext{REPL.LimitIO{…}}, ::MIME{Symbol("text/plain")}, X::Node{Machine{…}, typeof(transform)})
    @ MLJBase ~/.julia/packages/MLJBase/F8Zzu/src/composition/learning_networks/nodes.jl:303
  [5] show_repl(io::IO, mime::MIME{Symbol("text/plain")}, x::Any)
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:548
  [6] show_limited(io::IO, mime::MIME, x::Any)
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:517
  [7] (::REPL.var"#display##0#display##1"{REPL.REPLDisplay{REPL.LineEditREPL}, MIME{Symbol("text/plain")}, Base.RefValue{Any}})(io::Any)
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:540
  [8] with_repl_linfo(f::Any, repl::REPL.LineEditREPL)
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:833
  [9] display(d::REPL.REPLDisplay, mime::MIME{Symbol("text/plain")}, x::Any)
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:526
 [10] display
    @ ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:546 [inlined]
 [11] display(x::Any)
    @ Base.Multimedia ./multimedia.jl:340
 [12] (::REPL.var"#print_response##2#print_response##3")()
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:590
 [13] repl_backend_loop(backend::REPL.REPLBackend, get_module::Function)
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:445
 [14] start_repl_backend(backend::REPL.REPLBackend, consumer::Any; get_module::Function)
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:427
 [15] start_repl_backend
    @ ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:424 [inlined]
 [16] run_repl(repl::REPL.AbstractREPL, consumer::Any; backend_on_current_task::Bool, backend::Any)
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:653
 [17] run_repl(repl::REPL.AbstractREPL, consumer::Any)
    @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:639
 [18] run_std_repl(REPL::Module, quiet::Bool, banner::Symbol, history_file::Bool)
    @ Base ./client.jl:478
 [19] run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_file::Bool)
    @ Base ./client.jl:499
 [20] repl_main
    @ ./client.jl:586 [inlined]
 [21] _start()
    @ Base ./client.jl:561
Stacktrace:
 [1] print_response(errio::IO, response::Any, backend::Union{…}, show_value::Bool, have_color::Bool, specialdisplay::Union{…})
   @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:599
 [2] (::REPL.var"#print_response##0#print_response##1"{REPL.LineEditREPL, Pair{Any, Bool}, Bool, Bool})(io::Any)
   @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:558
 [3] with_repl_linfo(f::Any, repl::REPL.LineEditREPL)
   @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:833
 [4] print_response(repl::REPL.AbstractREPL, response::Any, show_value::Bool, have_color::Bool)
   @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:556
 [5] (::REPL.var"#do_respond#73"{…})(s::REPL.LineEdit.MIState, buf::Any, ok::Bool)
   @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:1192
 [6] run_interface(terminal::Base.Terminals.TextTerminal, m::REPL.LineEdit.ModalInterface, s::REPL.LineEdit.MIState)
   @ REPL.LineEdit ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/LineEdit.jl:2854
 [7] run_frontend(repl::REPL.LineEditREPL, backend::REPL.REPLBackendRef)
   @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:1663
 [8] (::REPL.var"#61#62"{REPL.LineEditREPL, REPL.REPLBackendRef})()
   @ REPL ~/.julia/juliaup/julia-1.12.0+0.x64.apple.darwin14/share/julia/stdlib/v1.12/REPL/src/REPL.jl:650
Some type information was truncated. Use `show(err)` to see complete types.

julia> 
```

Pull request description:
Closes #1022 

This PR also adds some show testing and removes some non-public API (`typeof(object).name.name`).

A line in operations.jl has been removed because it is not used anywhere.

Patch:
diff --git a/Project.toml b/Project.toml
index 23912096..c299e9dc 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,7 +1,7 @@
 name = "MLJBase"
 uuid = "a7f614a8-145f-11e9-1d2a-a57a1082229d"
 authors = ["Anthony D. Blaom <anthony.blaom@gmail.com>"]
-version = "1.9.1"
+version = "1.9.2"
 
 [deps]
 CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597"
diff --git a/src/composition/learning_networks/nodes.jl b/src/composition/learning_networks/nodes.jl
index bbcd0351..af51e5e2 100644
--- a/src/composition/learning_networks/nodes.jl
+++ b/src/composition/learning_networks/nodes.jl
@@ -270,8 +270,8 @@ _formula(stream::IO, X::AbstractNode, indent) =
     (print(stream, repeat(' ', indent));_formula(stream, X, 0, indent))
 _formula(stream::IO, X::Source, depth, indent) = show(stream, X)
 function _formula(stream, X::Node, depth, indent)
-    operation_name = string(typeof(X.operation).name.mt.name)
-    anti = max(length(operation_name) - INDENT)
+    operation_name = simple_repr(X.operation)
+    anti = max(length(operation_name) - INDENT, 0)
     print(stream, operation_name, "(")
     n_args = length(X.args)
     if X.machine !== nothing
@@ -287,9 +287,9 @@ function _formula(stream, X::Node, depth, indent)
         _formula(stream, X.args[k],
                         depth + 1,
                         indent + length(operation_name) - anti )
-        k == n_args || print(stream, ",")
+        print(stream, ",")
     end
-    print(stream, ")")
+    print(stream, crind(indent), ")")
 end
 
 function Base.show(io::IO, ::MIME"text/plain", X::Node)
@@ -303,7 +303,7 @@ function Base.show(io::IO, ::MIME"text/plain", X::Node)
     _formula(io, X, 4)
 end
 
-# for displaying withing other objects:
+# for displaying within other objects:
 function Base.show(stream::IO, object::Node)
     str = simple_repr(typeof(object)) * " $(handle(object))"
     mach = object.machine
diff --git a/src/operations.jl b/src/operations.jl
index d42689f2..0a824f5e 100644
--- a/src/operations.jl
+++ b/src/operations.jl
@@ -108,8 +108,6 @@ inverse_transform(mach::Machine; rows=:) =
                                 "must be explictly specified, "*
                                 "as in `inverse_transform(mach, X)`. "))
 
-_symbol(f) = Base.Core.Typeof(f).name.mt.name
-
 # catches improperly deserialized machines and silently fits the machine if it is
 # untrained and has no training arguments:
 function _check_and_fit_if_warranted!(mach)
diff --git a/src/show.jl b/src/show.jl
index 8b9201e8..722a9cd2 100644
--- a/src/show.jl
+++ b/src/show.jl
@@ -92,27 +92,12 @@ show_as_constructed(object) = show_as_constructed(typeof(object))
 show_compact(object) = show_compact(typeof(object))
 show_handle(object) = false
 
-# simplified string rep of an Type:
 function simple_repr(T)
-    repr = string(T.name.name)
-    parameters = T.parameters
-
-    # # add abbreviated type parameters:
-    # p_string = ""
-    # if length(parameters) > 0
-    #     p = parameters[1]
-    #     if p isa DataType
-    #         p_string = simple_repr(p)
-    #     elseif p isa Symbol
-    #         p_string = string(":", p)
-    #     end
-    #     if length(parameters) > 1
-    #         p_string *= ",…"
-    #     end
-    # end
-    # isempty(p_string) || (repr *= "{"*p_string*"}")
-
-    return repr
+    # get rid of type parameters:
+    output = split(repr(T), "{") |> first
+    # get rid of qualifiers:
+    output = split(output, ".") |> last
+    return output
 end
 
 # short version of showing a `MLJType` object:
@@ -165,7 +150,7 @@ function fancy(stream, object::MLJType, current_depth, depth, n)
         show(stream, object)
     else
         prefix = MLJModelInterface.name(object)
-        anti = max(length(prefix) - INDENT)
+        anti = max(length(prefix) - INDENT, 0)
         print(stream, prefix, "(")
         names = propertynames(object)
         n_names = length(names)


Test patch:
diff --git a/test/composition/learning_networks/nodes.jl b/test/composition/learning_networks/nodes.jl
index e79cec9d..0e13735e 100644
--- a/test/composition/learning_networks/nodes.jl
+++ b/test/composition/learning_networks/nodes.jl
@@ -481,6 +481,17 @@ y = source(7)
     end
 end
 
+@testset "show for nodes" begin
+    # https://github.com/JuliaAI/MLJBase.jl/issues/1022
+    X, y = make_blobs(3)
+    X, y = source.([X, y])
+    mach = machine(Standardizer(), X)
+    W = transform(mach, X)
+    str = sprint(show, MIME("text/plain"), W)
+    # smoke test:
+    @test contains(str, "formula:\n    transform")
+end
+
 end # module
 
 true
diff --git a/test/show.jl b/test/show.jl
index 44aff52c..e64439f4 100644
--- a/test/show.jl
+++ b/test/show.jl
@@ -1,5 +1,24 @@
 using .Models
 
+module FooFoo
+
+struct FooBar{T}
+    x::T
+end
+
+end
+
+using .FooFoo
+
+@testset "simple_repr" begin
+    @test MLJBase.simple_repr(typeof(fill(3, 1))) == "Vector"
+    @test MLJBase.simple_repr(typeof(FooFoo.FooBar(3))) == "FooBar"
+    # with module qualifiers within type parameters
+    object = FooFoo.FooBar(FooFoo.FooBar(rand(3)))
+    T = typeof(object) # Main.FooFoo.FooBar{Main.FooFoo.FooBar{Vector{Float64}}}
+    @test MLJBase.simple_repr(T) == "FooBar"
+end
+
 @testset "display of models" begin
     io = IOBuffer()
     show(io, KNNRegressor())