Sub module accessing a global method

I am fairly new to Julia :wink:

My question centers around a sub-module trying to call a method (not a function) where the method is in global space. I have a module named Nodes that wants to call a method transition(...). transition(...) exists within the global namespace yet Julia throws because it can’t find the method. I thought if a method was in the global space it was accessible from all modules without requiring imports.

The error below I get doesn’t seem to make sense to me:

ERROR: LoadError: MethodError: no method matching transition(::SplashScene)
Closest candidates are:
  transition(!Matched::Main.Ranger.Nodes.Scenes.SceneBoot) at /home/iposthuman/Documents/Development/Julia/Source/Ranger-Julia-SDL/nodes/scenes/scene_boot.jl:36
Stacktrace:
 [1] visit(::NodeManager, ::Float64) at /home/iposthuman/Documents/Development/Julia/Source/Ranger-Julia-SDL/nodes/node_manager.jl:74
 [2] run(::World) at /home/iposthuman/Documents/Development/Julia/Source/Ranger-Julia-SDL/game/game.jl:111
 [3] go() at /home/iposthuman/Documents/Development/Julia/Source/Ranger-Julia-SDL/examples/template0/main.jl:18
 [4] top-level scope at none:0
 [5] include at ./boot.jl:326 [inlined]
 [6] include_relative(::Module, ::String) at ./loading.jl:1038
 [7] include(::Module, ::String) at ./sysimg.jl:29
 [8] exec_options(::Base.JLOptions) at ./client.jl:267
 [9] _start() at ./client.jl:436
in expression starting at /home/iposthuman/Documents/Development/Julia/Source/Ranger-Julia-SDL/examples/template0/main.jl:22

Julia sees one of the methods transition(..SceneBoot) because I can import it:

using .Scenes:
    AbstractScene,
    enter_node, transition,
    REPLACE_TAKE

Yet varinfo() shows the transition method is present:

name                    size summary                
–––––––––––––––– ––––––––––– –––––––––––––––––––––––
Base                         Module                 
Core                         Module                 
GameScene          196 bytes DataType               
InteractiveUtils 163.926 KiB Module                 
Main                         Module                 
RAnim              6.567 KiB Module                 
RGame             23.112 KiB Module                 
RGeo              11.195 KiB Module                 
RMath             22.614 KiB Module                 
RNodes            33.537 KiB Module                 
RRendering        22.289 KiB Module                 
RScenes           10.746 KiB Module                 
Ranger           123.143 KiB Module                 
SplashScene        196 bytes DataType               
ans                  0 bytes Nothing                
build                0 bytes typeof(build)          
enter_node           0 bytes typeof(enter_node)     
exit_node            0 bytes typeof(exit_node)      
get_replacement      0 bytes typeof(get_replacement)
go                   0 bytes typeof(go)             
transition           0 bytes typeof(transition)  
visit                0 bytes typeof(visit)  

And running methods on transition clearly shows that a matching method exists for transition(..SplashScene):

julia> methods(transition)
# 2 methods for generic function "transition":
[1] transition(node::GameScene) in Main at /home/iposthuman/Documents/Development/Julia/Source/Ranger-Julia-SDL/examples/template0/game_scene.jl:56
[2] transition(node::SplashScene) in Main at /home/iposthuman/Documents/Development/Julia/Source/Ranger-Julia-SDL/examples/template0/splash_scene.jl:53

This leads me to think I need to import —somehow— into my Nodes module from Main.

My modules are arranged as follows:

Ranger   <== top module as shown above
  Nodes     <== aliased via a const as RNodes

Nodes module has a function called visit(...) inside the file node_manager.jl

function visit(man::NodeManager, interpolation::Float64)
    if is_empty(man.stack)
        println("NodeManager: no more nodes to visit.")
        return false
    end
    ... some stuff
    # If mouse coords changed then update view coords.
    # self.global_data.update_view_coords(&mut self.context);
    println("Transition..........")
    action = transition(man.stack.running_node)    <===== transition isn't found????
    println("action ", action)

    ... more stuff
    true # continue to draw.
end

My question is: how do I make the transition method(s) visible to visit? I feel fairly comfortable applying using and import but I could be missing something. At this point I am scratching my head trying to figure out what Julia expects.

The source is available at:
Ranger Julia

Any ideas or thoughts?

Thanks.

This is a lot of code to try to understand all at once, but I think I can make a guess as to what is going on. I suspect you are doing something like this:

julia> module Outer
         export f
         f(::Int) = 1

         module Inner
           using ..Outer
           f(::Float64) = 2 # BAD: this is *not* a new method for Outer.f, but instead a brand-new function with the same name.
           
           f(1) # this fails because Inner.f is a function with just one method: f(::Float64).
         end
       end
WARNING: replacing module Outer.
ERROR: MethodError: no method matching f(::Int64)
Closest candidates are:
  f(::Float64) at REPL[7]:7

Instead, you need to explicitly extend the method from the other module, just as you do with all other methods in Julia:

julia> module Outer
         export f
         f(::Int) = 1

         module Inner
           using ..Outer
           Outer.f(::Float64) = 2 # GOOD: we are explicitly adding a new method to Outer.f, not making a new function with the same name
           @show f(1) f(1.0) # this works!
         end
       end
WARNING: replacing module Outer.
f(1) = 1
f(1.0) = 2

Alternatively, instead of:

using ..Outer
Outer.f(...) = ...

you could also have done:

import ..Outer: f
f(...) = ...

which has the same effect.

The presence of nested modules doesn’t affect the behavior of this at all: you will experience the same issue regardless of whether Inner happens to live inside Outer or not.

5 Likes

Interesting. But what if the “outer” isn’t in a custom module but a method within Main? Your example gave me the clue I needed—Nice! And! I also learned from your example how to override a function using an explicit semantics—thanks.

In addition, I reduced my code down to an example similar to yours. What I was confused :thinking: about was importing from Main and I finally figured it out as shown below:

function transition(f::Float64)
  println(f)
end

function transition(i::UInt32)
  println(i)
end

module Ranger
  module Nodes
    using Main:transition  # <-- This is what I needed
    
    function visit()
      transition(1.0)
      transition(UInt32(3))
    end
  end
end

module Game
  using ..Ranger.Nodes:
    visit
    
  function run()
    visit()
  end
end

using .Game:
  run
  
function go()
  run()
end
  
go()

Now that I think about I could just make sure my Scenes are within a well defined module, for example GameInstance, and then have my engine expect that module to exist. :thinking:

Update: After looking at my code I realized I am in a chicken and egg scenario. My game constructs only after the engine construct, however, the engine is trying to import methods from the game construction.


include("../../ranger.jl")   # <-- Chicken

const RGame = Ranger.Game

include("build.jl")  # <-- Egg!

using .Ranger.Game:
    World

println("Running Template 0")

function go()
    world = RGame.initialize("Template 0", build)

    RGame.run(world);
end

Argh! Why doesn’t Julia have the ability to forward-declare. It would be nice if Julia had a Block type include that allow all code to parse prior to cross checking dependencies.

How do I get myself out of this conundrum? :face_with_raised_eyebrow:

Thanks rdeits.

This issue is resolved. Please see this related question:

How do I add a function/method to an already defined module