Julia introspection - get the name of the variable passed to the function

In Julia, is there a way to get the name of the passed function?

x = 10 function myfunc(a) # do something here end assert(myfunc(x) == "x") 

Do I need to use macros or is there my own method that provides introspection?

+3
source share
3 answers

Well, that contradicts me: technically this is possible in a very hacker mode, under one (rather limited) condition: the function name should have only one method signature. The idea is very similar to the answers to such questions for Python . Before demonstrating, I must emphasize that these are internal compiler details and are subject to change. In short:

 julia> function foo(x) bt = backtrace() fobj = eval(current_module(), symbol(Profile.lookup(bt[3]).func)) Base.arg_decl_parts(fobj.env.defs)[2][1][1] end foo (generic function with 1 method) julia> foo(1) "x" 

Let me emphasize once again that this is a bad idea and cannot be used for anything! (well, except for displaying the back panel). These are basically “stupid compiler tricks”, but I'm showing it because it can be some kind of tutorial to play with these objects, and the explanation does lead to a more useful answer to the explanatory comment from @ejang.

Explanation:

  • bt = backtrace() generates ... backtrace ... from the current position. bt is an array of pointers, where each pointer is a frame address in the current call stack.
  • Profile.lookup(bt[3]) returns a LineInfo object with the name of the function (and a few other details about each frame). Note that bt[1] and bt[2] are in the backtrace generation function itself, so we need to go further up the stack to get the caller.
  • Profile.lookup(...).func returns the function name (symbol :foo )
  • eval(current_module(), Profile.lookup(...)) returns the function object associated with the name :foo in current_module() . If we change the definition of function foo to return fobj , then pay attention to the equivalence of the foo object in the REPL:

     julia> function foo(x) bt = backtrace() fobj = eval(current_module(), symbol(Profile.lookup(bt[3]).func)) end foo (generic function with 1 method) julia> foo(1) == foo true 
  • fobj.env.defs returns the first Method entry from MethodTable for foo / fobj

  • Base.decl_arg_parts is a helper function (defined in methodshow.jl ) that extracts information about arguments from a given Method .
  • the rest of the indexing comes down to the argument name.

Regarding the limitation that a function has only one method signature, the reason is that all multiple signatures will be listed (see defs.next ) in MethodTable . As far as I know, there is currently no interface for obtaining a specific method associated with a given frame address. (as an exercise for the advanced reader: one way to do this would be to change the address search functionality in jl_getFunctionInfo to also return the jl_getFunctionInfo function name, which can then be re-associated with a specific method call, however I don’t think that we are currently saving reverse matching from a malformed name -> Method).

Note also that (1) backtracks are slow (2) Julia has no concept of a “functionally local” eval, so even if one has a variable name, I find it impossible to access the variable (and the compiler can completely exclude local variables, unused or otherwise, put them in a register, etc.)

Regarding the use of the introspection of the IDE style mentioned in the comments: foo.env.defs , as shown above, is one place to start "introspecting objects." On the debug side, Gallium.jl can check the local DWARF variable in this frame. Finally, JuliaParser.jl is a Julia parser implementation that is actively used in several IDEs to introspect high-level code blocks.

+4
source

You can capture the variable name with a macro:

 julia> macro mymacro(arg) string(arg) end julia> @mymacro(x) "x" julia> @assert(@mymacro(x) == "x") 

but, as others have said, I'm not sure why you need it.

Macros work in AST (code tree) at compile time, and x is passed to the macro as a symbol :x . You can turn a Symbol into a string and vice versa. Macros replace code with code, so @mymacro(x) simply pulled out and replaced with string(:x) .

+6
source

Another method is to use the vinfo function. Here is an example:

 function test(argx::Int64) vinfo = code_lowered(test,(Int64,)) string(vinfo[1].args[1][1]) end test (generic function with 1 method) julia> test(10) "argx" 

The above depends on the knowledge of the signature of the function, but this is not a problem if it is encoded inside the function itself (otherwise, macro magic may be required).

+2
source

All Articles