Differences between mutliple functions and function with multiple methods

For one of my projects, I have multiple implementations of the same analytical computations. These implementations offer different speed / precision trade-offs, and involve mostly a two step process where the entries and outputs are the same, but not the intermediate results. The final result is a matrix.

struct myintermediateresult1
...
end
struct myintermediateresult2
...
end
function myfirstmethod1(args...)::myintermediateresult1
...
end
function myfirstmethod2(args...)::myintermediateresult2
...
end
function mysecondmethod1(arg::myintermediateresult1)::Matrix{Float64}
...
end
function mysecondmethod2(arg::myintermediateresult2)::Matrix{Float64}
...
end

When I call this in my script, I use a symbol as argument of the calling function and wrap these methods in an if then else construct e.g.

if arg_symbol === :method1
    intermediateresult = myfirstmethod(args...)
else 
    intermediateresult = mysecondmethod(args...)
end

Therefore, the variable intermediateresult is not always of the same type.
JeffreySarnoff explained that one could also declare a single function with multiple methods using for instance

function myfirstmethod(args...,::Val{:method1})::myintermediateresult1
...
end
function myfirstmethod(args...,::Val{:method2})::myintermediateresult2
...
end

and avoid the if then else altogether.

What are the differences between these implementations, especially in a situation where I often compare the different implementations ? Is there a difference in terms of precompilation or efficiency ? This is probably related to the multiple dispatch (so I guess the second option is more julianic) but can someone highlight some major differences ?

If I understand you correctly, you have some distinct structs and some sets of methods, one method set for each struct type.

struct StructA
   value::Int32
end

struct StructB
  value::Float64
end

function f1_a(x::StructA) ...
function f1_b(x::StructB) ...
function f2_a(x::StructA) ...
function f2_b(x::StructB) ...

that will work without any ifelse-ing

1 Like

Well actually, if you look at myfirstmethod1 and myfirstmethod2, both share the same input arguments, so I cannot use directly multiple dispatch without specifying an additional argument (as proposed in the second implementation) or use an if the else construct.

We use Val for that sort of disambiguation
(note the curly braces in the function definition and the parens in the call).

function f(::Val{:asymbol}, args..) ...
function f(::Val{:bsymbol}, args..) ...
f(Val(:asymbol), ..) or f(Val(:bsymbol), ..) as you require

So you mean there is a difference between

function f(::Val{:asymbol}, args..) ...

and

function f(:asymbol, args..) ...

?

absolutely – the form with Val does what you want, the other is not Julia code.

Indeed ! That’s my bad. That is actually what I meant in my first post. I will edit accordingly.
However, my initial question remains… what are the main differences between these two implementations ?

In my opinion, one is cleaner, easier to maintain over time (particularly if you add more cases)
In this particular case, performance differences should be inconsequential unless you do this a huge number of times [in that case benchmark each approach].

I’d rather follow the first reply in this topic and wrap input arguments in some sort of a buffer:

struct Method1Input
    ...
end

struct Method2Input
    ...
end

function first_step(input::Method1Input)::IntermediateResult1
    ...
end

function first_step(input::Method2Input)::IntermediateResult2
    ...
end

Then, instead of

first_step(Val(:method1), args...)

just call

first_step(Method1Input(args...))

Another way is to pass a function as an extra argument:

function first_step(method::Function, args...)
    method(args...)
end

function method1(args...)
   ...
end

function method2(args...)
   ...
end