Julia: call a function with a given string

Does Julia support reflection like java?

I need something like this:

str = ARGS[1] # str is a string # invoke the function str() 
+8
reflection julia-lang
source share
1 answer

Good way

The recommended way to do this is to convert the function name to a character and then look at that character in the corresponding namespace:

 julia> fn = "time" "time" julia> Symbol(fn) :time julia> getfield(Main, Symbol(fn)) time (generic function with 2 methods) julia> getfield(Main, Symbol(fn))() 1.448981716732318e9 

You can change Main here to any module to look only at the functions in this module. This allows you to limit the set of available functions to only those available in this module. You can use the bare module to create a namespace that contains only the functions you populate without importing the entire name from Base by default.

Bad way

Another approach, which is not recommended, but for which many people seem to strive for, is to build a line for the code that calls the function, and then parse that line and evaluate it. For example:

 julia> eval(parse("$fn()")) # NOT RECOMMENDED 1.464877410113412e9 

Although it is tempting simple, it is not recommended, as it is slow, fragile and dangerous. The analysis and evasion code is inherently much more complicated and therefore slower than finding a name in a module - a name search is just a search in a hash table. In Julia, where code is accurately compiled rather than interpreted, eval is much slower and more expensive because it not only includes parsing, but also generates LLVM code, runs optimization runs, emits machine code, and then finally calls the function. Parsing and dodging a string is also fragile, as all intended values ​​are discarded when the code turns into text. Suppose, for example, that someone accidentally provides an empty function name - then the fact that this code is intended to call a function is completely lost when the syntax is accidentally similar:

 julia> fn = "" "" julia> eval(parse("$fn()")) () 

Unfortunately. This is not what we wanted. In this case, the behavior is pretty harmless, but it can be much worse:

 julia> fn = "println(\"rm -rf /important/directory\"); time" "println(\"rm -rf /important/directory\"); time" julia> eval(parse("$fn()")) rm -rf /important/directory 1.448981974309033e9 

If user input is invalid, this is a massive security hole. Even if you trust the user, they can still accidentally enter input that will do something unexpected and bad. A search by name approach avoids these problems:

 julia> getfield(Main, Symbol(fn))() ERROR: UndefVarError: println("rm -rf /important/directory"); time not defined in eval(::Module, ::Any) at ./boot.jl:225 in macro expansion at ./REPL.jl:92 [inlined] in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46 

The purpose of finding the name and then calling it as a function is explicit, and not implied in the generated string syntax, so in the worst case, an error occurs that the strange name is undefined.

Performance

If you are going to call a dynamically defined function in an inner loop or as part of some recursive calculation, you will need to avoid getfield with every function call. In this case, all you have to do is bind const to a dynamically defined function before defining an iterative / recursive procedure that calls it. For example:

 fn = "deg2rad" # converts angles in degrees to radians const f = getfield(Main, Symbol(fn)) function fast(n) t = 0.0 for i = 1:n t += f(i) end return t end julia> @time fast(10^6) # once for JIT compilation 0.010055 seconds (2.97 k allocations: 142.459 KB) 8.72665498661791e9 julia> @time fast(10^6) # now it fast 0.003055 seconds (6 allocations: 192 bytes) 8.72665498661791e9 julia> @time fast(10^6) # see? 0.002952 seconds (6 allocations: 192 bytes) 8.72665498661791e9 

The f binding should be constant for optimal performance, because otherwise the compiler cannot know that you will not change f to point to another function at any time (or even something that is not a function) so it should emit code, which dynamically looks f dynamically at each iteration of the loop is actually the same as if you manually call getfield in the loop. Here, since f const , the compiler knows that f cannot change, so it can emit fast code that simply calls the correct function directly. But the compiler can sometimes do even better than in this case, in fact it embeds an implementation of the deg2rad function, which is just multiplication by pi/180 :

 julia> @code_llvm fast(100000) define double @julia_fast_51089(i64) #0 { top: %1 = icmp slt i64 %0, 1 br i1 %1, label %L2, label %if.preheader if.preheader: ; preds = %top br label %if L2.loopexit: ; preds = %if br label %L2 L2: ; preds = %L2.loopexit, %top %t.0.lcssa = phi double [ 0.000000e+00, %top ], [ %5, %L2.loopexit ] ret double %t.0.lcssa if: ; preds = %if.preheader, %if %t.04 = phi double [ %5, %if ], [ 0.000000e+00, %if.preheader ] %"#temp#.03" = phi i64 [ %2, %if ], [ 1, %if.preheader ] %2 = add i64 %"#temp#.03", 1 %3 = sitofp i64 %"#temp#.03" to double %4 = fmul double %3, 0x3F91DF46A2529D39 ; deg2rad(x) = x*(pi/180) %5 = fadd double %t.04, %4 %6 = icmp eq i64 %"#temp#.03", %0 br i1 %6, label %L2.loopexit, label %if } 

If you need to do this with many different dynamically defined functions, and you use Julia 0.5 (nightly), then you can even pass in the function that needs to be called as an argument:

 function fast(f,n) t = 0.0 for i = 1:n t += f(i) end return t end julia> @time fast(getfield(Main, Symbol(fn)), 10^6) 0.007483 seconds (1.70 k allocations: 76.670 KB) 8.72665498661791e9 julia> @time fast(getfield(Main, Symbol(fn)), 10^6) 0.002908 seconds (6 allocations: 192 bytes) 8.72665498661791e9 

This generates the same fast code as one fast argument above, but will generate a new version for every other function f with which you call it. If you use Julia 0.4 (stable), this will work, but it will be slower and will not compile the new version for you. If you want to achieve the same effect on 0.4, you need to create your own function yourself, using a little metaprogramming.

+15
source share

All Articles