I’m relatively new to Julia but came across a recurring problem in several projects now.

It often happens that I want to use the same general algorithm but with different functions inside. For example here I have an Euler Algorighm that integrates a function, which gets passed as an argument:

function euler(f,t0,tn,Δt,x₀,par)
T = t0:Δt:tn
F = Vector{typeof(x₀)}(undef,length(T))
F[1] = x₀
for (n,t) ∈ enumerate(T[2:end])
F[n+1] = F[n] .+ f(F[n],par) .* Δt
end
return F
end

Different functions can have different numbers of parameters. Thats why I pass the “static” parameter as tuple. But there are cases, where the function that gets integrated needs also t as an argument, or the hole history of the process F or other “dynamical” parameters. Therefore or, I write the function call, and therefore all functions that I want to use the algorithm on in the most general way, which then becomes unreadable and hard to modify or I copy the outer algorithm - the euler function - and paste every time a different loop where I sometimes call f(F[n],par), or f(F,n,par) or f(t,F[n],par) ect. But this code is replicative which I usually try to avoid.

Does anybody know an elegant solution to the general problem of? I hope I could explain myself. Please let me know if one does not understand the problem.

The static parameters can be more cleanly passed with closures:

const a = 5
g(x,a) = a*x
f(func,x) = func(x)
#call with
f(x -> g(x,a), x)

For func to have an arbitrary number of parameters I think you have to use something like:

const a = 5
g(x,y,a) = a*x*y
f(func,args...) = func(args...)
#call with
f((x,y)->g(x,y,a),x,y)

For the later I am not sure if I have the most common an clean solution though.

add on:

One intermediate alternative to the “args…” is to use an inner call with all possible parameters (if that is known), and then use a closure if less parameters are used:

julia> const a = 5
5
julia> g(x,a) = a*x
g (generic function with 1 method)
julia> function f(func,x)
t = 1
func(x,t)
end
f (generic function with 1 method)
julia> f((x,t) -> g(x,a), 2)
10

EDIT: I should have read Leandro’s answer more carefully. What I suggest is basically his “add-on” suggestion.

I think that you should have a single version of the euler function in which you call f passing everything you may need to pass, and then, if necessary, you wrap the function f that you pass to euler as a parameter in an anonymous function that discards the unused parameters.

Another possible solution is inversion of control: rather than having euler() both specify how to go from one time step to the next and do the time integration loop, you can have a function

euler_step(f,t,dt,x0) = x0 + f(t,x0) * dt

and then one or more functions

function integrate(step,f,t0,tn,dt,x0)
x = x0
for t in t0:dt:tn
x = step(f,t,dt,x)
end
return x
end

which do the actual time integration. This allows you to have multiple integrate() functions depending on e.g. whether or not you want to keep track of the history, but on the other hand doesn’t require duplicating the time stepping logic.