If module A
has an extension B
with a function b_fun()
, how can I call this function? I was assuming I could do:
using A
using A.B: b_fun
b_fun()
The namespacing for package extensions has me a little confused.
If module A
has an extension B
with a function b_fun()
, how can I call this function? I was assuming I could do:
using A
using A.B: b_fun
b_fun()
The namespacing for package extensions has me a little confused.
Ahh, I guess the answer is I need to do
ext = Base.get_extension(A, :B)
ext.b_fun()
Key takeaways:
get_extension
and assigning an extension’s properties to variables do not behave like imports and are not intended for routine use. Reflection and select testing seems fine.Is it that package extensions aren’t intended to or can’t export new names?
They can’t
Thanks, a workaround I found was to do something like this where A
is the base package and B
is the extension:
module A
newfunction() = error("This function is available
after importing B and calling `newfunction` with
arguments x/y/z...")
export newfunction
end
And in B
:
module B
using A
A.newfunction(x,y,z) = ...
end
That’s not a workaround, that’s exactly how package extensions are supposed to work
To extend Michaels answer – that is how packages are supposed to work – you could also leave the error-part to Julia itself (leading to a nicer Method not found
error including suggestions in case you just misspelled it or missed a signature)
module A
@doc """
newfunction()
This function is meant to provide functionality when both A and (package-from-extension) are loaded and then provides the functionality that...
"""
newfunction()
export newfunction
end
in my experience the error messages you get this way are more helpful.
Shouldn’t that rather be:
function newfunction end
?
I think both should work, but yours is indeed nicer.
I came to my solution, since usually I document a certain signature of a function.
I’d suggest not doing this. It’s not unusual for another package or extension to add methods to a public function originating in a package, but it’s not great for the original package alone to expose a public function that uses up the zero-argument method only to advise the user to import something else. Note that a call with more arguments will only throw a MethodError
, so a user would have to guess at an atypical practice of interactively calling the listed zero-argument method for the advice. The name and implementation seem more appropriate in a separate package that depends on the packages that the extension depended on. The intention wasn’t irrational; it is cleaner to limit the number of importable packages when many methods depend on various mixes of packages, and extensions accomplish that smoothly in addition to loading them only when needed. But going too far can erode the principle of module independence (at the extreme, you merge everything into one package), and no feature can substitute careful module design.
newfunction(args...; kwargs...)
as a universal fallback might not be an absolute no-go, though. It all depends on the API of your package, how the error message gets displayed to the user, and whether you can come up with something more informative than a MethodError
in the specific context of your package.
I’ve done something similar in my own package:
The important difference there is your package does provide implementation in addition to the erroring fallback.
I think it would make sense to document how extensions interact with namespacing and the practical implications (ie that they can’t export new symbols into their parent module, so different strategies are needed, as outlined in this thread).
I don’t see an open issue for this, I will wait for feedback before opening one though.
The correct way to think about them is like they are a separate package that gets automatically import
ed once all the “triggers” of it has been loaded and that you can get a handle to with get_extension
.
So if you want you can do
BExt = Base.get_extension(A, :B)
using .BExt
if you want to get the exported things from the extension. But (just like with a separate package) it is not like its exported symbols will just appear somewhere else (like in the parent package)
I was under the impression that this was discouraged because the extension name (:B
in this example) was not meant to be part of the public API of the “extended” package (A
in the example). Am I mistaken?
Yes, you are “allowed” do this from the same places as you are allowed to use the internals of A
.