Hi, I need some help in understanding whether value types are the right solution for my situation.
I have some simulation that can be solved in different ways.
When I run it, I can choose different parameters such as the mass of the particles but I also want to choose the algorithm that will solve it. Every time that I run the simulation it opens a new julia session and runs only one of the methods for the simulation.
Until now, the way I handled it was to comment/uncomment the relevant lines of code.
I’ll try to sketch here some MWE
# Main.jl
module Simulation
include("algorithm.jl")
mutable struct SimParam
m::Float64
end
function run(param::SimParam, method::String)
solve(param, method)
end
end
# algorithm.jl
function solve(param, method)
...
# shared code part 1
...
if method == "1"
part1_method1(param)
elseif method == "2"
part1_method2(param)
end
...
# shared code part 2
...
if method == "1"
part2_method1(param)
elseif method == "2"
part2_method2(param)
end
# and so on ...
end
function part1_method1(param::SimParam)
# ...
end
function part1_method2(param::SimParam)
# ...
end
function part2_method1(param::SimParam)
# ...
end
function part2_method2(param::SimParam)
# ...
end
My concerns are:
I don’t want the compiler to care about the different methods when there is no need to, because it will increase the run time of each one of them. (If I wrong here not please tell me)
There is a lot of shared code, and the connection between the different methods is very tight. So if I separate them completely, it will be a nightmare to maintain and debug the code. On the other hand, starting to have a lot of elseif inside the code does not seem to me to be the right answer.
I’ve read about value types that will help me to remove all the elseif and can help readability, but the manual warns against using it freely. So I’m not sure whether my situation is good for value types or not.
Other option is to have some file that has the definition like
# Defs.jl
const method ="1"
This file can be changed before each run and because of the use of the const, the compiler should ignore all the different methods.
I guess the simulation takes more than a few microseconds, thus probably both things there are not important. (the problem is using that inside a hot loop of your code, when the type changes).
A common pattern is to use:
julia> struct Method1 end
julia> struct Method2 end
julia> simulation(params,method::Method1) = println("method1!")
simulation (generic function with 1 method)
julia> simulation(params,method::Method2) = println("method2!")
simulation (generic function with 2 methods)
julia> simulation("abc",Method1())
method1!
julia> simulation("abc",Method2())
method2!
or, alternatively, you could parameterize the Params with the method, such as:
julia> struct Params{Method}
method::Method
end
julia> simulation(params::Params{Method1}) = println("method 1!")
simulation (generic function with 3 methods)
julia> simulation(params::Params{Method2}) = println("method 2!")
simulation (generic function with 4 methods)
julia> simulation(Params(Method1()))
method 1!
julia> simulation(Params(Method2()))
method 2!
I think the choice must be on the basis on how clean the input becomes for the user, in your case.
(from the perspective of code sharing between the methods there is no difference between any of the alternatives)
The first option seems better for me, I guess I can also define nested methods via inheritance.
I just want to be sure, us that consider value types? If so, why does the warning against it not apply here?
I am not sure if all that is considered “type values”, I would think those are more like this:
julia> simulation(params,::Val{:method1}) = "method 1!"
simulation (generic function with 1 method)
julia> simulation(params,::Val{:method2}) = "method 2!"
simulation (generic function with 2 methods)
julia> simulation("abc",Val(:method1))
"method 1!"
julia> simulation("abc",Val(:method2))
"method 2!"
There is nothing wrong in using anything like what was discussed here in your context, I think. The problem of those is if you had, for example, a loop that ran your simulation thousands of times, each simulation was very short, and you changed the simulation method at each iteration of the loop, in such a way that at every iteration of the loop you would need to call a different method.