Julian Alternative to Template by Value

I often find myself writing functions where the algorithm implemented depends on the value of an integer. For example, in numerical analysis I often have some numerical method, with 1st, 2nd, 3rd, and 4th order versions of the method each handwritten (because the generalization to each order is not so easy that I can do it programatically). I will have one function wrapper that selects the function with the correct order. E.g.

function wrapper([args], order)
    if (order == 1)
        return first_order_method([args])
    if (order == 2)
        return second_order_method([args])
    if (order == 3)
        return third_order_method([args])
    [...]
    end
end

If I were writing C++ code, I would do something like

template <int order>
[return type] wrapper([args])
{
    if (order == 1)
        return first_order_method([args]);
    if (order == 2)
        return second_order_method([args]);
    if (order == 3)
        return third_order_method([args]);
}

Using this structure in C++, there would be 3 compiled functions, one for each value of the order. And presumably the compiler would optimize out the if statements for the orders that aren’t used. The julia version, on the other hand, would only have one compiled function (for one set of type in [args]) regardless of the value of args. Whether first_order_method, second_order_methdo. etc is called will be determined at runtime.

What is the Julian way of dealing with this? Should I just take the hit and structure my code in such a way that the if statements in a situation like this are inexpensive compared to the function being called in the if statement? Is the compiler able to deal with this in a way I’m not aware of? As I understand it, the more things which are known at compile time the better, which is why I wanted something more along the lines of the C++ version.

You could lift the order parameter to the type domain by using Val.
Oftentimes, this pattern is counterproductive, because it stresses the compiler unnecessarily but this should not be the case here, since the number of possible implementation is small:

method(args, order) = method(args, Val(order)

method(args, ::Val{1} = "implementation 1"

method(args, ::Val{2} = "implementation 2"
3 Likes

Not sure about the Julian way, but you can use Val as a “compile time known constant”:

algorithm(x, ::Val{1}) = println("this is the first order algorithm")
algorithm(x, ::Val{2}) = println(" second order")
algorithm(x) = algorithm(x, Val(1))
3 Likes

That works. Thanks!