I often write code that is parameterized by a small set of related ‘inner’ functions (f, g,…). The functions are related in the sense that they all do different computations but they share some parameters.
I include a simplified code example below of what I have in mind.
My first instinct for this is to simply include the inner functions as arguments in whatever code I’m writing. If there is more than 1 or 2 I might wrap them in a type or tuple to reduce clutter when passing through many functions.
I also realized that you can achieve the same results using the type system and multiple dispatch. You define some ‘parameter type’ and then write methods using that type.
Are there situations where / reasons why the type-based approach is a bad idea? Is one approach better style than the other? Perhaps the type-based approach is more opaque, but it also has the advantage that it automatically ensures that all the methods used within a function call are consistent (i.e., they are generated from the same parameters). This could be achieved with the other approach, but is not automatic and would require an extra wrapper function.
module FirstModule # type-based approach
function foo end
function bar end
abstract type AbstractParameters end
# the abstract type is not really necessary
# but could be used to provide a descriptive
# error / fallback method if appropriate
function dosomething!(y, x, p::AbstractParameters)
for i in eachindex(x)
y[i] = foo(x[i], p) + bar(foo(x[i], p), p) ^ 2
end
return y
end
export dosomething!,
AbstractParameters
end
module SecondModule # just functions approach
function dosomething!(y, x, f, g)
for i in eachindex(x)
y[i] = f(x[i]) + g(f(x[i])) ^ 2
end
return y
end
export dosomething!
end
# type-based approach
struct MyParams{T} <: FirstModule.AbstractParameters
# these parameters could be anything in general, but most likely
# just numbers or perhaps arrays
θ::T
end
FirstModule.foo(x, p::MyParams) = x ^ p.θ
FirstModule.bar(x, p::MyParams) = x - p.θ
# function-based approach
function makefuncs(θ)
foo(x) = x ^ θ
bar(x) = x - θ
return foo, bar
end
# Check they are equivalent
x = rand(10000)
y1 = similar(x)
FirstModule.dosomething!(y1, x, MyParams(2.0))
y2 = similar(x)
SecondModule.dosomething!(y2, x, makefuncs(2.0)...)
all(y1 .== y2)