Thank you for your resposes @abraemer !
Yes, in a sense is due to “external constraints”: many users of the code are not experienced Julia programmers and it would be complicated for them to use the code properly.
The code is already largely wrapped within functions. In fact, it is composed of two main parts: a library, which is “static” and does not depend on the user input, and a user interface, responsible of putting together the calls to the library depending on the user input. The latter is the problem of course, because it is at this level when I need to collect all the user input, find out the parameters to use for the inference and the constant quantities, and decide which functions are needed. At this stage I also take out of the inference constant quantities (i.e., I precompute them and store in an external constant structure passed to the likelihood).
Macros anyway have been my first attempt when I started this project a couple of years ago, but things with macros get very quickly quite confusing, especially if one needs to use macros within macros (which is something I had to do at a certain point).
However, following your kind suggestion, I will reconsider if there is a way to implement things with a macro.
I know Turing.jl and I have been trying to use for my purposes in the past, but with no success at all. It is certainly a very interesting project and I can see a lot of uses for it, but probably not for what I am trying to do (modeling of strong gravitational lenses), as the complexity of the systems and the number of free parameters is really large. For this reason it is really vital to have an highly optimized code (and yes, I do not use untyped global variables and all my critical functions do not have dynamic dispatch).