I was wondering if if statements based on types are optimized at compile time. Here are few ways of writing the same code. Is any of them better than the rest? Is the extra cost only a couple of if statements at run time and not worth it? Is one more julian?

Using strings:

function solve(alg, args...)
<Code to preprocess>
if alg == "FastApprox"
<Code for Fast Approximate algorithm>
elseif alg == "SlowAccurate"
<Code for Slow Accurate algorithm>
else
error()
end
<Code to postprocess>
end

Using type checks:

function solve(::Type{Value{Alg}}, args...) where Alg
<Code to preprocess>
if Alg == :FastApprox
<Code for Fast Approximate algorithm>
elseif Alg == :SlowAccurate
<Code for Slow Accurate algorithm>
else
error()
end
<Code to postprocess>
end

Using multiple dispatch on value:

algorithm(::Type{Value{:FastApprox}}, args...) = <Code for Fast Approximate algorithm>
algorithm(::Type{Value{:SlowAccurate}}, args...) = <Code for Slow Accurate algorithm>
function solve(alg::Type{Value{Alg}}, args...) where Alg
<Code to preprocess>
algorithm(alg, args...)
<Code to postprocess>
end

Suppose you discover some amazing new algorithm that is both fast and accurate, and you want to add it to your package. With the first two options, you need to go back and modify all of your existing code to account for the new method. But with the third option, you can just add a method to algorithm, and everything else will just work.

I suppose that is a long winded way of saying that the true power of multiple dispatch isn’t really the compile time optimization. It is the ability to write code that is specific, but also easily maintainable.

Also consider making structs not just using Val. Even more idiomatically is making these structs callable and calling them directly instead of using the function algorithm.

abstract type AbstractAlg end
struct FastApprox <: AbstractAlg end
struct SlowAccurate <: AbstractAlg end
(::FastApprox)(args...) = <Code for Fast Approximate algorithm>
(::SlowAccurate)(args...) = <Code for Slow Accurate algorithm>
function solve(alg::AbstractAlg, args...)
<Code to preprocess>
alg(args...)
<Code to postprocess>
end
solve(FastApprox(), args...)
solve(SlowAccurate(), args...)

This approach is also very natural to use when the algorithm has parameters which can be made fields of the struct and used in the algorithm’s call function.