Understand Main and Base module behavior

I’ve read the Julia reference section on modules carefully but I still don’t understand exactly what’s going on below. These are two separate sessions.


First session


# Create a simple type and define show() for it.
julia> struct Foo <: Integer x end

julia> show(z::Foo) = print(z)
show (generic function with 1 method)

julia> show(0)
ERROR: MethodError: no method matching show(::Int64)
You may have intended to import Base.show
Closest candidates are:
  show(::Foo) at REPL[2]:1
Stacktrace:
 [1] top-level scope at none:0

julia>

Second run



julia> show(0)
0
julia> struct Foo <: Integer x end

julia> show(0)
0
julia> show(z::Foo) = print(z)
ERROR: error in method definition: function Base.show must be explicitly imported to be extended
Stacktrace:
 [1] top-level scope at none:0

julia>

I understand that if I want to extend Base.show I should import it, but I don’t understand exactly what’s going on here. The manual says that in all Julia sessions using Base is done implicitly to make Base’s methods available, and that Base’s methods are available for extension. In the first session I can define show on my type without conflict but Base.show is not available via default. In the second session, invoking show(0) seems to have bound show() to Base.show such that extending it with my type causes an error. Can someone explain in more detail the process?

Thanks,
-Tom

What happens is you created a new Main.show which is not the same as Base.show.

julia> Main.show == Base.show
false

Instead, you need to define Base.show(z::Foo) = print(z) so the show method is the same.

If you want to extend an existing method, you need to be more specific, otherwise you create a completely new function. You can only have one show method name in your scope.

Finally, the behavior also depends on whether you called the Base.show before making your new definition to replace it. If you dont call it first, you can create a new show.

I encountered a similar issue when i created the ForceImport.jl package.

3 Likes

A quick question then. If you do define a new function i.e. Main.show() why dosn’t it use that one given that you are working within Main?

Shouldn’t that still run when calling show from Main? Alternatively put, why does Julia call Base.show when calling a show function from Main?

By defining show(z::Foo) you’re hiding the Base.show method for Int64. After running the code by @tomf without first printing/showing something else (i.e. using Base.show in some way), there quite literally is no method available accepting an Int64 for the function show:

julia> struct Foo <: Integer
              x
       end

julia> show(z::Foo) = print(z)
show (generic function with 1 method)

julia> methods(show)
# 1 method for generic function "show":
[1] show(z::Foo) in Main at REPL[2]:1

Compare with an invocation of methods(show) in a new session:

julia> methods(show)
# 295 methods for generic function "show":
[1] show(io::IO, opt::Base.JLOptions) in Base at options.jl:54
[2] show(io::IO, r::LinRange) in Base at range.jl:383
[3] show(io::IO, r::UnitRange) in Base at range.jl:693
[4] show(io::IO, r::Base.OneTo) in Base at range.jl:694
[...]

# you get the idea                
1 Like

Thanks for the answers. This takes some getting used to. I can import show’s methods from Base and then extend show with my definition, but I can’t define Main.show first then import the other methods from Base to extend it.
I can accept that but I don’t understand it.

You can still extend it, you just have to qualify which show you want to extend. In this case like this: Base.show(x::Foo) = print("Hello"). Dispatch will still choose the right method when calling with a Foo.

In general, when extending it’s a good idea to explicitly qualify which function you’re extending in order to prevent ambiguities based on the history of the runtime.

1 Like