Hi!
Inside a function, I would like to evaluate a module’s function based on two inputs: 1) the module itself and 2) the function’s name as a string. Here is the code:
module A
function foo()
println("This is A.foo")
end
end
function bar(m::Module, s::String)
# evaluate "m.s"
end
bar(A, "foo")
I have several questions:
How can I evaluate "m.s" inside the bar function to produce the expected output?
julia> bar(A, "foo")
This is A.foo
Do you have suggestions about better practices to achieve what I would like to do? I explain a bit more: the bar function will belong to a package that will handle some user-defined modules (in our example: A would be defined by a user). At end, a user would have to code module A and to write the functions inside (such as foo) in a config file. This config file design choice makes it easier to pass the function names as strings. While I am confident about the fact that I shall pass the function’s name as input, I do not know if it is a good thing to pass a whole module as input.
In answer to your broader question about whether this is a good approach in general, I would
ask why the package that bar belongs to needs to interact with modules at all. If bar
can be passed a module object, why can’t it just directly be passed foo?
This is related to some design choice: from the user’s point of view, we have to provide 1) the A module containing some functions such as foo and 2) a config file listing the functions inside A that will be used during the global execution. To make it clearer, say we have foo1 and foo2 inside A. The user could then be willing to use bar with maybe only foo1 during the global execution. Later, during another run, the user would be using foo1andfoo2. Truth is that I will be passing the list of used functions as strings and all this could be achieved simply with the config file.
I would avoid relying on eval for this, instead using getfield or getproperty on the module object:
julia> module A
foo() = "Hello"
end
Main.A
julia> function bar(mod, name)
fun = getfield(mod, name)
fun()
end
bar (generic function with 1 method)
# Passing the name directly as a Symbol
julia> bar(A, :foo)
"Hello"
# Or converting the String to a Symbol
julia> bar(A, Symbol("foo"))
"Hello"
Thanks! What is the advantage of getfield over eval?
I noticed allocation and computational improvements:
module A
function foo()
println("This is A.foo")
end
end
function bar1(m::Module, s::String)
m.eval(Meta.parse(s))()
end
function bar2(m::Module, s::String)
getfield(m, Symbol(s))()
end
s = "foo"
@btime bar1(A, s) # 28.833 μs (20 allocations: 608 bytes)
@btime bar2(A, s) # 8.229 μs (6 allocations: 144 bytes)
eval can evaluate any Julia code and as such can do almost anything. Which makes it harder for the compiler to reason about your code: it cannot assume anything. And it has to compile things.
On the other hand, getfield makes it easier to reason about your code: all that is needed is to look up some function name in a namespace. In particular there is no need to compile anything new.
I’m not sure I understand the relationship between foo1 and foo2 here… Are they different implementations of the same function? Or two sub-steps within foo?
In other words, what does it mean that bar can use foo1 alone or foo2 alone or (foo1 and foo2)?
Functions in A such as foo1 and foo2 are meant to be different. To simplify, say we would like to execute them sequentially. This makes a comprehensive example I believe:
module A
function foo1()
println("Foo 1")
end
function foo2()
println("Foo 2")
end
end
function bar(m::Module, l::Array{String})
for s in l
getfield(m, Symbol(s))()
end
end
Two different usages depending on what functions of A the user wants to execute:
And the reason I do not directly pass an array containing the functions A.foo1 and A.foo2 themselves is that I want the user to specify the function names inside a configuration file. The user-defined module A is something fixed but depending on the case, one would want to specify different functions of A in the configuration file.
It looks like you’re using modules mostly like dictionaries mapping names to functions. So to answer your broader question from the OP, your current architecture seems to do the job.