Check each call has a corresponding method

I have a annoying problem which might be stupid. It is quite often that after I start my program for 10 min, julia tells me that a function call does not have a matched method (“no method matching”).

Usually the problem is simple and requires only a single line fix. But it’s annoying that my careless faults are found only when the function is really being called. I think this is the price we need to pay for the JIT and multiple dispatch approach, but I’m wondering if there is a tool like JET.jl which can find this kind of errors?

I don’t care about type stability at this moment, but just want to make sure each function call can be executed in the runtime. For example, something like @check_method func(bar) would check all function calls following func(bar), and give up and report the error if it finds one.

I am not a good programmer, so this makes really miss languages like c++ which can do more checks before runtime.

JET already does this, provided the method dispatch is done at compile-time, successful or not. This is consistent with AOT-compiled languages checking things before runtime.

julia> begin
       foo() = bar()
       bar() = baz(1)
       baz() = 0
       using JET
       end

julia> @report_call foo()
═════ 1 possible error found ═════
┌ foo() @ Main ./REPL[1]:2
│┌ bar() @ Main ./REPL[3]:1
││ no matching method found `baz(::Int64)`: baz(1)

If the method dispatch is done at runtime, then you can’t know if it’s an issue until runtime. Unlike non-interactive languages, the method could be implemented afterward at top-level (if not there, then you run into world age restrictions that exist to enable optimizations including static dispatch), and the compiled runtime dispatch code will work without any changes. I find Cthulhu.jl to spot runtime dispatches a bit better, then it’s up to you whether you improve type stability until the dispatch moves to compile-time or you make sure to implement all the methods needed there.

I rhink here there is also an issue of wrkflow.
In Julia is not like a compiled language where you have your long simulation program, you throw it, wait 20 minutes and get the reaults and then you code something, compile/run/wait again and so on.
At least while developing the program/model, with tiols like Revise, you can actually build piece by piece, a bit like a spider :-).
So you are in a certain state of your model, you have a call error, you solve the issue and you continue (till the next one :slight_smile: )…

Ok thanks. I think I want to check even if the dispatch is at runtime, but as you said, this is difficult. I guess we need to run the code in some way.

Yes, I also feel that issue is related to my workflow. For development, this kind of problems is minimal, as you said. The issue happens when I start running the simulation and want to do some small fixes in the middle of the code.

This might be solved by a better design of the code, so that I don’t to need wait so long before touching the error. I need to think about it.

It really is quite difficult to identify “careless faults” the more flexible a language is. When a function call can take varying number of arguments, which can be done with default arguments or varargs even without multimethods or function overloading, then it’s hard to deduce baz(1) was supposed to be baz(). Static dispatch can catch unimplemented calls, but if I had implemented baz(::Int), then the typo is fundamentally silent. It’s like accidentally indexing i in 1:n÷2 over an array that is 0-based; it’s working code and the program can’t know I had different intentions.

It’s much harder to check things before a runtime dispatch. There’s no inheritance and single dispatch to guarantee an implemented fallback method, so there’s a risk you run into a combination of types that don’t reach 1 implemented method. Statically, you could at least check if no method exists for the arity of the function call, but that’s not very helpful with unspecified type information. For example, if I implemented baz(::String), then static dispatch of baz(1) can report an error, and I can further infer I made a typo. A runtime dispatched baz(x) could very well happen to reach baz(::String) every single time, which is again working code that can’t know my intentions.

Things that help is breaking up large functions into smaller short-running ones you can check interactively and proofread a bit easier. It’s not possible sometimes, but this could also be done to break up a long-running program. The final product could be a user-friendly call, but you would work with its callees and intermediate results. I’ve been bit before by an hours-long computation that ends by saving a plot, but I was too inexperienced to even think of saving the data anywhere before plotting, and a plotting call error tossed out an afternoon’s results.