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.
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,
You should not use type annotations if they are not required for safety or multiple dispatch. E.g. it is unlikely that firstfunction
should only work for Float64, probably it should work for all numbers, therefore write
function firstfunction(x::Real) # or just firstfunction(x)
return 2*x
end
There is no performance penalty for defining functions more general.
Back to the topic: For applying a function to a matrix / vector, etc. the simplest way is to use broadcasting:
A = rand(10, 10) # 10x10 matrix
B = firstfunction.(A) # apply element-wise
If you want to define the way how to apply another function by yourself, you can use functions as input parameters, e.g.:
thirdfunction(f, x) = f(f(x))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With