Unexpected problem with previously working code - multiple dispatch seems to be failing

I came back from vacations and started to work on my code again. Some code that was working previously doesn’t seem to work anymore, while I don’t think I made any modifications to the code (updated my machine, but nothing major).

The issues goes along the lines of having a bit of code as:

foo(;a::Type1, b::Int, c:: ... ) = ...
foo(;a::Type2) = ...

When calling foo with a Type2, julia seems to want to use the first function, even though they are incompatible. Previously, this bit of code was working as intended. I am encountering this issue with multiple functions working with multiple dispatch.

A practical example in my code would be


function get_eigenvector_x_q_space(;x::Vector, q::Vector, eigenvectors, eigenstates_analytical_e, eigenstates_analytical_ph, eigenvector_index::Int, n_basis_states_e::Int, n_basis_states_ph::Int, atol = TOLERANCE_WAVE_FUNCTION_NORMALIZATION_2DOF) 

# some more code 

end


function get_eigenvector_x_q_space(;eigenvector_index::Int, k_y_index::Int, params::SimulationParameters, sim_results::SimulationResults2D, atol = TOLERANCE_WAVE_FUNCTION_NORMALIZATION_2DOF)

    # construction the wave function for the full state using the basis 

    @unpack n_basis_states_e, n_basis_states_ph = params

    x = sim_results.x_grid_array[1]
    q = sim_results.q_grid_array[1]
    eigenvectors = sim_results.eigenvectors_array[k_y_index]
    eigenstates_analytical_e = sim_results.eigenstates_analytical_e_array[k_y_index]
    eigenstates_analytical_ph = sim_results.eigenstates_analytical_ph_array[k_y_index]

    get_eigenvector_x_q_space(x = x, q = q, eigenvectors = eigenvectors, eigenstates_analytical_e = eigenstates_analytical_e, eigenstates_analytical_ph = eigenstates_analytical_ph, eigenvector_index = eigenvector_index, atol = atol, n_basis_states_e = n_basis_states_e, n_basis_states_ph = n_basis_states_ph)

end

Calling

get_eigenvector_x_q_space(eigenvector_index=1, k_y_index=1, params = params_int, sim_results = sim_results_int)

results in


ERROR: UndefKeywordError: keyword argument `k_y_index` not assigned
Stacktrace:
 [1] 
   @ Main ~/Documents/postdoc_freiburg/valery/quantum_hall_effect_cavity/julia/quantum_hall_with_cavity/2d_states_representation.jl:64
 [2] top-level scope
   @ ~/Documents/postdoc_freiburg/valery/quantum_hall_effect_cavity/julia/quantum_hall_with_cavity/main.jl:155
Some type information was truncated. Use `show(err)` to see complete types.

julia> show(err)
1-element ExceptionStack:
LoadError: UndefKeywordError: keyword argument `k_y_index` not assigned
Stacktrace:
  [1] get_eigenvector_x_q_space(; eigenvector_index::Int64, k_y_index::Int64, params::SimulationParameters, sim_results::SimulationResults2D, atol::Float64)
    @ Main ~/Documents/postdoc_freiburg/valery/quantum_hall_effect_cavity/julia/quantum_hall_with_cavity/2d_states_representation.jl:64
  [2] top-level scope
    @ ~/Documents/postdoc_freiburg/valery/quantum_hall_effect_cavity/julia/quantum_hall_with_cavity/main.jl:155
  [3] eval
    @ ./boot.jl:385 [inlined]
  [4] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
    @ Base ./loading.jl:2076
  [5] invokelatest(::Any, ::Any, ::Vararg{Any}; kwargs::@Kwargs{})
    @ Base ./essentials.jl:892
  [6] invokelatest(::Any, ::Any, ::Vararg{Any})
    @ Base ./essentials.jl:889
  [7] inlineeval(m::Module, code::String, code_line::Int64, code_column::Int64, file::String; softscope::Bool)
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.120.2/scripts/packages/VSCodeServer/src/eval.jl:271
  [8] (::VSCodeServer.var"#69#74"{Bool, Bool, Bool, Module, String, Int64, Int64, String, VSCodeServer.ReplRunCodeRequestParams})()
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.120.2/scripts/packages/VSCodeServer/src/eval.jl:181
  [9] withpath(f::VSCodeServer.var"#69#74"{Bool, Bool, Bool, Module, String, Int64, Int64, String, VSCodeServer.ReplRunCodeRequestParams}, path::String)
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.120.2/scripts/packages/VSCodeServer/src/repl.jl:276
 [10] (::VSCodeServer.var"#68#73"{Bool, Bool, Bool, Module, String, Int64, Int64, String, VSCodeServer.ReplRunCodeRequestParams})()
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.120.2/scripts/packages/VSCodeServer/src/eval.jl:179
 [11] hideprompt(f::VSCodeServer.var"#68#73"{Bool, Bool, Bool, Module, String, Int64, Int64, String, VSCodeServer.ReplRunCodeRequestParams})
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.120.2/scripts/packages/VSCodeServer/src/repl.jl:38
 [12] (::VSCodeServer.var"#67#72"{Bool, Bool, Bool, Module, String, Int64, Int64, String, VSCodeServer.ReplRunCodeRequestParams})()
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.120.2/scripts/packages/VSCodeServer/src/eval.jl:150
 [13] with_logstate(f::Function, logstate::Any)
    @ Base.CoreLogging ./logging.jl:515
 [14] with_logger
    @ ./logging.jl:627 [inlined]
 [15] (::VSCodeServer.var"#66#71"{VSCodeServer.ReplRunCodeRequestParams})()
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.120.2/scripts/packages/VSCodeServer/src/eval.jl:263
 [16] #invokelatest#2
    @ ./essentials.jl:892 [inlined]
 [17] invokelatest(::Any)
    @ Base ./essentials.jl:889
 [18] (::VSCodeServer.var"#64#65")()
    @ VSCodeServer ~/.vscode/extensions/julialang.language-julia-1.120.2/scripts/packages/VSCodeServer/src/eval.jl:34
in expression starting at /home/benoitseron/Documents/postdoc_freiburg/valery/quantum_hall_effect_cavity/julia/quantum_hall_with_cavity/main.jl:155
julia> 

I am not sure how to attack this problem. It feels like I am missing something elementary, but I am quite confident that this code was working fine previously. Renaming the first function to some other name, to avoid the use of multiple dispatch, solves the issue, but is not a very elegant solution as this problem seems to be popping up everywhere in my code.

See the documentation about methods:

Keyword arguments behave quite differently from ordinary positional arguments. In particular, they do not participate in method dispatch.

Since you are using the same name for the same signature (ignoring keyword arguments) you will overwrite some methods too. You can see this if you start julia with --warn-overwrite=yes:

julia> f(; x = 1) = x
f (generic function with 1 method)

julia> f(; y = 1) = y
WARNING: Method definition f() in module Main at REPL[1]:1 overwritten at REPL[2]:1.
WARNING: Method definition kwcall(NamedTuple{names, T} where T<:Tuple where names, typeof(Main.f)) in module Main at REPL[1]:1 overwritten at REPL[2]:1.
f (generic function with 1 method)

Note that there is still just one method, the one with y as keyword argument:

julia> f(x = 2)
ERROR: MethodError: no method matching f(; x::Int64)

Closest candidates are:
  f(; y) got unsupported keyword argument "x"
   @ Main REPL[2]:1

julia> f(y = 2)
2
4 Likes

If you did want to use keyword arguments for everything, you could forward the arguments to another function.

julia> struct Type1 end

julia> struct Type2 end                                               

julia> foo(;a::Union{Type1, Type2}, kwargs...) = _foo(a; kwargs...)
foo (generic function with 1 method)


julia> _foo(a::Type1; b::Int) = @info "Type1 says hello" b                 
_foo (generic function with 1 method)

julia> _foo(a::Type2) = @info "Type2 dice hola"
_foo (generic function with 2 methods)


julia> foo(; a = Type1(), b=2)
┌ Info: Type1 says hello
└   b = 2

julia> foo(; a = Type2())
[ Info: Type2 dice hola

Thanks, this seems neat. I’ve never seen this “method” before, is it considered standard? Does it have a technical name?

Thanks a lot, this solved the issue. I am wondering why keyword arguments would behave differently, however? What was the reason for this design choice? This seems quite counter-intuitive to me.

See this thread: What is the motivation for Julia not dispatching on keyword arguments?

And the linked github issue: keyword arguments · Issue #485 · JuliaLang/julia · GitHub

The key issue seems to be that if you have n optional arguments, generating a method with and without each one would mean 2^n methods.