8

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

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

Do I need to use macros or is there a native method that provides introspection?

ejang
  • 3,982
  • 8
  • 44
  • 70
  • 1
    No, this is not possible. I'm not aware of any mainstream language in which this is a good idea (where it is even available). It might help to clarify what problem you are ultimately trying to solve... – Isaiah Norton Oct 04 '15 at 02:01
  • 1
    when defining variables in the Matlab or Octave IDEs, the variables in the current Module/scope show up in a side panel, which is useful to know what variables exist in the workspace. In julia, one can get all variables by doing names(current_modules()) – ejang Oct 04 '15 at 04:20
  • 2
    a macro accepting the function definition would make this possible (by inserting a local definition calculated from argument expression) – Dan Getz Oct 04 '15 at 09:56
  • 1
    @ejang those introspection features are implemented outside of the function body, for example by hooking in at some stage of parsing or syntax lowering, or by generating meta information about a function that can be discovered and displayed by a debugger (for example, [DWARF](https://en.wikipedia.org/wiki/DWARF)). – Isaiah Norton Oct 04 '15 at 15:47
  • 1
    see also: https://groups.google.com/forum/#!topic/julia-users/-Ov5mEZd4zg – Isaiah Norton Oct 04 '15 at 17:27

3 Answers3

10

You can grab 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'd need that.

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

Tom Breloff
  • 1,782
  • 9
  • 15
  • 1
    I find this useful for informative warning messages similar in spirit to the assert macro but when I don't want to throw an error. Follow the julia docs at http://docs.julialang.org/en/stable/manual/metaprogramming#building-an-advanced-macro and modify the macro according to your needs – Fred Schoen Feb 13 '17 at 10:23
5

Ok, contradicting myself: technically this is possible in a very hacky way, under one (fairly limiting) condition: the function name must have only one method signature. The idea is very similar the answers to such questions for Python. Before the demo, I must emphasize that these are internal compiler details and are subject to change. Briefly:

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 re-emphasize that this is a bad idea, and should not be used for anything! (well, except for backtrace display). This is basically "stupid compiler tricks", but I'm showing it because it can be kind of educational to play with these objects, and the explanation does lead to a more useful answer to the clarifying comment by @ejang.

Explanation:

  • bt = backtrace() generates a ... backtrace ... from the current position. bt is an array of pointers, where each pointer is the address of a frame in the current call stack.
  • Profile.lookup(bt[3]) returns a LineInfo object with the function name (and several 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 (the symbol :foo)
  • eval(current_module(), Profile.lookup(...)) returns the function object associated with the name :foo in the current_module(). If we modify the definition of function foo to return fobj, then note the equivalence to 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 the MethodTable for foo/fobj

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

Regarding the restriction that the function have only one method signature, the reason is that multiple signatures will all be listed (see defs.next) in the MethodTable. As far as I know there is no currently exposed interface to get the specific method associated with a given frame address. (as an exercise for the advanced reader: one way to do this would be to modify the address lookup functionality in jl_getFunctionInfo to also return the mangled function name, which could then be re-associated with the specific method invocation; however, I don't think we currently store a reverse mapping from mangled name -> Method).

Note also that (1) backtraces are slow (2) there is no notion of "function-local" eval in Julia, so even if one has the variable name, I believe it would be impossible to actually access the variable (and the compiler may completely elide local variables, unused or otherwise, put them in a register, etc.)

As for the IDE-style introspection use mentioned in the comments: foo.env.defs as shown above is one place to start for "object introspection". From the debugging side, Gallium.jl can inspect DWARF local variable info in a given frame. Finally, JuliaParser.jl is a pure-Julia implementation of the Julia parser that is actively used in several IDEs to introspect code blocks at a high level.

Community
  • 1
  • 1
Isaiah Norton
  • 4,205
  • 1
  • 24
  • 38
  • 1
    we can also use these tricks make a generic version of [Tom's macro](http://stackoverflow.com/a/32934241/508431) above: `macro funcargs(func); fobj = eval(current_module(), func) ;Base.arg_decl_parts(fobj.env.defs)[2][1]; end` ..... (doing this without `eval` left as an exercise!) – Isaiah Norton Oct 04 '15 at 17:19
  • I believe this doesn't actually solve the problem @ejang was looking at. In the original post, it is the name of the passed variable `x` that was the desired output, not the function argument name. It's a bit confusing between the question reprex and this reprex because `x` is used in different ways. I'm in a similar context, where such a response is useful when `myfunc()` is called repeatedly somewhere, and with many successful calls. When a failure occurs, you want to know what the *input* was that caused it, so including the passed input name in the error message is helpful. – Daniel Egan Jul 04 '19 at 11:24
  • @DanielEgan would a debugger help?: https://julialang.org/blog/2019/03/debuggers ... I did misinterpret the question (pattern-matched too quickly with similar ones). I guess you could do what was requested by looking at the backtrace and then parsing the original source code in the calling location (using e.g. CSTParser.jl). But that's still very hacky: the basic issue stands that by design, functions aren't supposed to reason about their calling environment. Some languages like R do support a feature like this, which may be the origin of this type of question; but that feature has costs. – Isaiah Norton Jul 05 '19 at 14:17
3

Another method is to use the function's vinfo. 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 knowing the signature of the function, but this is a non-issue if it is coded within the function itself (otherwise some macro magic could be needed).

Dan Getz
  • 17,002
  • 2
  • 23
  • 41