juliamultiple-dispatch

Efficient way to implement multiple dispatch for many similar functions


I am writing some software that involves a library of various functional forms of a quantity. I want to leverage Julia's multiple dispatch, but want to know if there's a more efficient way to implement this procedure.

Consider, for example, a library that contains the following two functions

function firstfunction(x::Float64)
    return 2*x
end

function secondfunction(x::Float64)
    return x^2
end

I would also like to implement multiple dispatch methods that can apply these functional forms to an vector of values, or an array of vectors (matrix). I could do this as follows

function firstfunction(x::Float64)
    return 2*x
end

function firstfunction(xs::Vector{Float64})
    f = similar(xs)
    for i = 1:size(xs, 1)
        f[i] = 2*xs[i]
    end
    return f
end

function firstfunction(xss::Matrix{Float64})
    f = similar(xss)
    for i = 1:size(xss, 1)
        for j = 1:size(xss, 2)
            f[i, j] = 2*xss[i, j]
    end
    return f
end

function secondfunction(x::Float64)
    return x^2
end

function secondfunction(xs::Vector{Float64})
    f = similar(xs)
    for i = 1:size(xs, 1)
        f[i] = xs[i]^2
    end
    return f
end

function secondfunction(xss::Matrix{Float64})
    f = similar(xss)
    for i = 1:size(xss, 1)
        for j = 1:size(xss, 2)
            f[i, j] = xss[i, j]^2
    end
    return f
end

But since all three versions of the function use the same kernel, and the actions of the various dispatches are the same across all functional forms, I'd like to know if there's a more efficient way to write this such that defining a new function for the library (e.g thirdfunction) only involves explicitly writing the kernel function, rather than having to type out 2*n essentially identical functions for n functional forms in the library.


Solution

  • Just do:

    function thirdfunction(x::Union{Number, Array{<:Number}})
        return x.^0.5
    end
    

    This is the beauty of multiple-dispatch in Julia:

    julia> thirdfunction(4)
    2.0
    
    julia> thirdfunction([4,9])
    2-element Array{Float64,1}:
     2.0
     3.0
    
    julia> thirdfunction([4 9; 16 25])
    2×2 Array{Float64,2}:
     2.0  3.0
     4.0  5.0
    

    Note that however in your case it might make sense to have only a single representation of a function and let the user decide to vectorize it using the dot operator (.).

    function fourthfunction(x::Real)
        min(x, 5)
    end
    

    And now the user just needs to add a dot when needed:

    julia> fourthfunction(4)
    4
    
    julia> fourthfunction.([4,9])
    2-element Array{Int64,1}:
     4
     5
    
    julia> fourthfunction.([4 9; 16 25])
    2×2 Array{Int64,2}:
     4  5
     5  5
    

    Since vectorizing in Julia is so easy you should consider this design whenever possible,