Programatic function (and variable) names

I have a use case, where code needs to generate multiple sets of functions.
So functions do not get overwritten, I want to add a unique pre-/postfix equal for each function in a set.

Specifically, functions

  • init()
  • run()

  • are created for every set.

How do I name all functions in the nth set of functions

  • init_n()
  • run_n()

Suppose, the definition of functions stays the same, I just want to change their names.
This probably falls into the area of macro programming but I could not find an illustrative example that would have helped me.

I want such functionality:
a = “Function_name”
@macro a()
println("Hello)
end

Function_name()
Hello

I would be happy for any pointer you may be able to provide.

Why not use anonymous functions? Can you give an example of what kind of function you want to generate, and why?

3 Likes

You could do something like this, but such module surgery is not necessarily recommended (I am pretty confident there is a better way to achieve your goals without doing such things):

function rename_function!(f, newname::Symbol)
    eval(:(global $(newname) = $f))

    # without renaming here also - 
    # the function name will still be print/shown with the old name
    typeof(eval(newname)).name.mt.name = newname
end

function fgenerator()
    eval(:(init_1() = "initial name: init_1"))
end

fgenerator()
@info names(Main) # init_1 already defined

rename_function!(init_1, :init_2)

@info names(Main) # init_2 defined, init_1 printing as init_2

@info init_2() # prints "initial name: init_1"

# shown as init_2
@info init_2

# also shown as init_2
@info init_1

# revert
rename_function!(init_2, :init_1)

# shown as init_1
@info init_2

# also shown as init_1
@info init_1

At this point, I am unaware of any method to delete a name once defined.

Again, while the above code is possible, it is not something that I would recommend doing.

1 Like

The macro you’re looking for is @eval:

a = Symbol("function_name")
@eval function $a()
    println("Hello")
end
julia> function_name()
Hello

This is often used for things like programmatically generating wrappers for external libraries (look at the BLAS and LAPACK wrappers in the LinearAlgebra standard library).

2 Likes

This does not seem like a use case for eval. Dynamically generating identifiers/names is universally discouraged in every programming language. (Google “eval is evil” or something like that).

Use anonymous functions. Your use case is not very clear, but you could for example collect them in a vector, and call them as init[n]().

5 Likes

Making many functions with the exact same body means you’re going to compile their function calls to identical native code many times, which is overhead to be avoided if possible. What is a set here, and why do they need init, run, etc each?

3 Likes

To expand on what others have said, a vector of functions could be a much better alternative than creating a set of similar functions with generated names. It could look like that:

julia> const init = map(1:5) do n
          function ()   # Anonymous function but will be available as init[n]
             println("Hello, I'm function $n")
           end
       end
5-element Vector{var"#6#8"{Int64}}:
   [...]

julia> init[3]()
Hello, I'm function 3
2 Likes

Thanks for the example. I wasn’t aware that definitions could be changed like that. I thought of them more as immutable once defined.

Yes, that is exactly my use case. I need to write a plugins using c functions (defined in Julia) that interact with a shared library.

Thank you for bringing this up. I definitely see massive issues server-side exposing input to eval to a user - similar to SQL injection. Client-side it’s probably only additional overhead thats a downside, right?

I really like this approach. If i can create a c-callable function pointer off of an anonymous function, I will use it.

If i can create a C-callable function pointer using anonymous functions, I will probably use them. Is this possible?

Yes, as long as you’re not on ARM or PowerPC: Calling C and Fortran Code · The Julia Language

If the C API is properly designed, however, this should not be needed — properly designed C APIs that accept a function pointer should also take a void* argument that is passed through to your function, in order to simulate closures (as otherwise the code cannot be re-entrant). In this case you can use the void* to pass through an anonymous function to a thin static wrapper routine, as e.g. explained here (somewhat old, but the basic principle still holds).

(And if the C API is not re-entrant, then you might as well make your Julia callback non-re-entrant too. Just write a thin static wrapper that calls your “actual” callback stashed in a global.)

2 Likes

Yes, I just tried to make a concise problem statement. In fact, a slight change may be required in any of the generated functions’ bodies - but not enough to make writing them by hand efficient.

Thanks for mentioning the performance hit. This is indeed a concern. In my case only 2-3 sets may have to be generated at the beginning of execution - which should not be too bad.

It’s just bad practise all round.

1 Like

You didn’t go much into what functions you might want to generate (and have slight body changes at runtime).

So just a wild shot here: check out the DynamicExpressions.jl package.

You might use the package to design the runtime-changeable parts of your functions and store the dynamic expressions in a Ref (so you don’t need to recompile the calling function).

In that way, you might be able to have functions that behave like the function body was changed (e.g., operating on the Ref containing the dynamic expressions from outside the function) without actually having a function body change (and without performance hit).

This might not be out of the box 100% compatible with your use case - but if performance is paramount, you can design your project around it.

2 Likes

Any way to make that slight change a method argument or a field of a callable instance? Those won’t need you to generate multiple methods to compile separately, but if those changes mean different types then the same method will be compiled many ways, which doesn’t save much compilation time but does trim down the number of functions.