Hello!
I want to write a general function with several subroutines. I would like this method to be able to use different functions as subroutines, by taking them as parameters, as shown in the toy example below.
function myfunction(input::AbstractMatrix,
preprocess::Function,
update!::Function)
A = preprocess(input)
# Some common code
# [...]
for i in 1:100
# Some common code
# [...]
updater!(A)
end
# Some common code
# [...]
return A
end
My motivation is that a big part of the code will be common and only the preprocess and the updater will be specific.
My question is: how can I pass parameters to preprocess
and updater!
, if the different options for these subroutines take different parameters? For example I have one preprocesser that takes no parameter, another that takes one Integer, another that takes a matrix, etc.
I would like to know what is the best practice in this case, in terms of “standard Julia style”, performance, and readibility.
The options I see are:
- Duplicate the code, write specific versions of
myfunction
and do not pass functions as parameters
- Have a big structure holding all possible parameters for all differents preprocessers, pass it as a parameter in
myfunction
and in A = preprocess(input, param_struct)
and make each preprocesser use only the ones it needs.
- Do some kind of homemade curryfication, where I specialize a preprocesser with its parameters before passing it to
myfunction
.
All these options seem unsatisfying to me, so maybe I’m asking the wrong question and I should see the problem another way. Anyway I would be very thankful for any help, comment, or advice.
Thanks for reading, and please ask if something is not clear!
Nicolas
I guess the key question is where those parameters are coming from and how you want to manage them. From your description it seems like you could just do
function myfunction(input::AbstractMatrix,
preprocess::Function,
preprocess_args::Tuple,
updater!::Function,
updater_args::Tuple
)
A = preprocess(input, preprocess_args...)
# Some common code
# [...]
for i in 1:100
# Some common code
# [...]
updater!(A, updater_args...)
end
# Some common code
# [...]
return A
end
but I might be missing something.
Maybe you could use keyword arguments Functions · The Julia Language
The differences to optional arguments is that the order doesn’t matter but only the keywords. Notices that one uses ;
instead of ,
to capture keyword argument lists.
E.g.
function myfunction(input::AbstractMatrix,
preprocess::Function,
updater!::Function;
args...
)
A = preprocess(input; args...)
# Some common code
# [...]
for i in 1:100
# Some common code
# [...]
updater!(A; args...)
end
# Some common code
# [...]
return A
end
For the user calling your function it is enough to write
myfunction(input, preprocess, updater!, alpha = 10, beta = 4 )
Do some kind of homemade curryfication, where I specialize a preprocesser with its parameters before passing it to myfunction
.
Why are you disparaging the approach of enforcing that the user-provided function have a specific arrow type (e.g. (Int, Int) -> Float64)
)? This seems like very normal behavior for higher-order functions.
1 Like
Thanks a lot for your answers. Both solutions from Gunnar and Steffen would work in my case. I include below a toy MWE for future reference, and also in case I’m doing something wrong you can maybe comment it. I am not very experienced in Julia.
With Gunnar’s solution:
function myfunction(input::AbstractMatrix,
preprocess::Function,
preprocess_args::Tuple)
A = preprocess(input, preprocess_args...)
return A
end
function nonneg(A)
B = copy(A)
B[B.<0] .= 0
return B
end
function addx(A, x)
return A .+ x
end
a = randn(2,3)
display(a)
a1 = myfunction(a, nonneg, ())
display(a1)
a2 = myfunction(a, addx, (5,))
display(a2)
With Steffen’s:
function myfunction(input::AbstractMatrix,
preprocess::Function;
args...)
A = preprocess(input; args...)
return A
end
function nonneg(A)
B = copy(A)
B[B.<0] .= 0
return B
end
function addx(A; x)
return A .+ x
end
a = randn(2,3)
display(a)
a1 = myfunction(a, nonneg)
display(a1)
a2 = myfunction(a, addx, x=5)
display(a2)
I think I will use the second solution as the syntax will be clearer in my case.
About jonhmyleswhite’s comment: it would probably be cleaner the way your propose it, but in my case I really need functions that take different number and types of parameters. Maybe my use case is not a good one for higher-order programming (I have no experience at all) but I thought this could help me have less duplicate code.
For the record, this is how a currying approach could look:
function myfunction(input::AbstractMatrix,
preprocess::Function)
A = preprocess(input)
return A
end
function nonneg(A)
B = copy(A)
B[B.<0] .= 0
return B
end
function addx(A, x)
return A .+ x
end
a = randn(2,3)
a1 = myfunction(a, nonneg)
a2 = myfunction(a, x -> addx(x, 5))
2 Likes