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