I have a user defined module which invokes a method defined in another module from a package. How can I get the name of the invoking user-defined module, so that my package can include/eval code back into the user’s module? Thanks!
Hard to say more without context, but this is almost certainly a horrible idea, even if implementable.
But I would imagine that the answer is not even well defined: if H.h
calls G.g
, which calls f
, should f
think it is called from H
or G
? What if the compiler inlines G.g
, which it is free to do by default?
Then, eval
ing code to some other module is also a fragile solution.
That said, the innermost function may examine stracktrace()
, and figure something out from that. But I imagine that the problem could be solved some other way, eg returning a callable to the user. An MWE that illustrates the problem (not this particular quesiton, but the original problem) would be helpful.
I think __module__
is what you are looking for:
module M
macro g()
__module__
end
end
julia> M.@g()
Main
This is for macros and can be used to see what module the macro is expanded in.
Well if you want a function you can just pass the calling module as an argument:
module M
g(m) = @info "module $m is calling"
end
module N
import ..M
f() = M.g(@__MODULE__)
end
N.f()
Right, and in the rare cases where you need this information in a function, you should often have the caller invoke the function via a macro.
For example, define a function foo(m::Module, args...)
that takes a module as an argument (like include_string
), and then define a macro @foo(args...)
that calls foo(__module__, args...)
.
This way the module to use is unambiguous: it’s the module in whose code the @foo
call is located.
But it’s not a good idea to do this. If you want to do something as intrusive as eval’ing code into a module that’s not yours then do at least require that to be explicitly passed.
Thank you all for the feedback!
For a bit of context, trying to abstract the use case as much as possible:
- there’s a user module
UserModule
- and a package
Package
-
Package
exposes a functionx
.
When Package.x()
is invoked from UserModule
, Package
does a lot of complex processing and generates a new function y
which is tailor-made for UserModule
- and executes the y
function (basically x
is a wrapper for y
). So y
is a function specialised for and determined by the state of UserModule
. This function y
is also cached in a .jl
file and loaded from memory or from the file in subsequent calls, as long as the state of UserModule
does not change. If UserModule
changes, y
is automatically regenerated and the whole process restarts.
This works great, but at the moment y
gets invoked in the scope of Package
when what I really need is to invoke it in the scope of UserModule
.
Options:
1 - my initial idea, to eval in the scope of UserModule
. I agree, it’s a code smell.
2 - instead of executing y
and returning the output, just return y
. I don’t like this since it would force the user to do x()()
which is ugly.
3 - use a macro @x - with Package
returning an expression (the function) and the macro executing the function. Not sure if this works well (I’m still unsure about the scope of variables within macros and macros invocations, I can’t see the whole thing inside my head, I need to try it out).
@kristoffer.carlsson and @stevengj - yes, that’s most likely the way to go about it, I’ll give it a try, thanks for your help!
If you are generating the function y
as source code, you may want to reconsider — that’s a very odd and non-idiomatic way to implement a higher-order function. A function x()
returning another function (a higher-order function) is perfectly normal, but is usually implemented simply by returning a closure.
Could you elaborate on why you think the return value of x()
needs to be a function defined in the scope of UserModule
, as opposed to simply a closure?
@stevengj In order to avoid that the user does x()()
I feel that if I force the user to use x()()
I expose the implementation. The user shouldn’t care that I return a function which then she needs to invoke. The user wants the result of invoking x
, without caring about what I need to do.
===
The fact that the function is stored as source code is not really relevant - I’m doing this only to cache the result of the expensive processing. When I load the .jl
file (upon app restart) I get back the function. So for the purpose of this discussion, caching is irrelevant.
But isn’t that the whole point of x()
, to return a function? Can you summarize what you are actually trying to do?
Sure
It’s about an MVC app - and UserModule
is the controller while x
is a function which renders the view. So instead of x
there’s html($state_vars)
.
The view itself is defined by the user say in a file called view.html
and looks like HTML:
<div>
<p>
<span>$( embedded Julia code here using $state_vars )</span>
</p>
</div>
Because parsing is expensive, when Package.html($state_vars)
is invoked for the first time, Package
converts the above HTML into something like:
function view_html()
Package.div(
Package.p(
Package.span( embedded Julia code here using $state_vars )
)
)
end
This output is cached to the .jl
file and next time, instead of parsing the HTML view file, this Julia code is used (obviously, if the view_html()
function is already in memory, it’s used directly). When view_html()
is invoked, this outputs an HTML string with the embedded Julia code dynamically executed. So basically on the subsequent execution, html($state_vars)
skips parsing and executes view_html($state_vars)
.
We’re talking about compiled view templates which can execute embedded Julia at each request, without reparsing the HTML tags.
So the user does not expect a function - the user expects the view file to be parsed and rendered and returned to the client.
Oh, okay, so basically you are providing a specialized version of include
that includes user code in a particular format (HTML with embedded code). Yes, then a macro is the right choice.
Something analogous is implemented by the NBInclude.jl package: it offers an @nbinclude(filename)
macro that includes user code stored in a special format (as a Jupyter notebook). This then calls a function nbinclude(module, filename)
that actually evaluates the code.
You might want to look at the nbinclude
implementation to see how it implements things like line-number tracking (for backtraces) and how it calls Base._include_dependency(module, path)
to make sure precompilation works properly.
Correct, yes, that’s the best way of putting it!
Excellent, I’ll check it out and go with a @html
macro of sorts.
Thanks for your patience and your help, much appreciated!
I’ve made good progress with this but I’m stuck at the last step
Basically, I got to the point where I can invoke a function and everything works (generated function is executed in the correct module and returned and then invoked). It looks like this:
function new()
html(@__MODULE__, :books, :new, book = Book()) |> Base.invokelatest
end
How can I wrap this in a @html
macro which would be invoked as:
@html(:books, :new, book = Book())
That is, to avoid passing the module explicitly (as it’s available in the macro in __module__
) and automatically invoke the function. The keyword arguments are variable (between 0 and n
).
I find the technique discussed in this thread very useful for writing my package. To see the technique in action, please see the code in the linked repository.