As I said above, you should check Reexport to see how you can implement this. In particuclar, reading the source code is just wrong… You can just import the package and loop over the exported names.
It wasn’t clear to me how you can get a list of exported names from a defined module. is it names? that’s a very handy function, I was looking for one like that for a while. It only contains exported items?
macro force(import_module)
import_module.head ≠ :using && throw(error("not a using statement"))
pkg = import_module.args[1]
s = :(Expr(:toplevel,[Expr(:import,Symbol($(string(pkg))),j) for j ∈ names($pkg)]...))
return :(import $pkg; eval($s))
end
Still opposed to it being a standard feature? And yea, I updated it to handle multiple pkgs in the repo.
That’s exactly why I expect you to be able to do it from what’s in Reexport.
Yes. Being simple has nothing to do with whether it should be in Base. You can easily write a function called plus_three(x) = x + 3 and that’s not at all a good reason for it to go into Base. In fact, the exact opposite is true. If it wasn’t so easy to implement, that will be an argument to improve the support in Base.
That doesn’t seem like a good criterion. The NPM disaster happened exactly because people made a lot of one-liner packages. My preference is for not putting things like this into a package, but if it’s not in Base then it has to be its own package.
No, but various proposals for forced/automatic resolution of import conflicts have been discussed extensively and repeatedly (see e.g. Function name conflict: ADL / function merging? - #38 by kristoffer.carlsson for a recent example) and most of the core developers seem to be generally opposed to anything in Base that promotes silent overwriting of symbols without explicitly enumerating them. You can certainly submit a PR to julia, of course, but I personally doubt it will get much traction.
Did some more testing, and found that the macro only works if it is defined in Main. If you do
julia> using ForceImport
julia> module Foo
export +
+() = "hey"
end
Foo
julia> @force using Foo
ERROR: UndefVarError: Foo not defined
I think this might have to be included with Base afterall, because as a package it doesnt work, unless I am misunderstanding something about how macro evaluation works.
The macro does work if I manually enter it into the REPL or put it into .juliarc.jl.
See the macro hygiene section in the manual, in particular the esc I’ve already mentioned above. And as I said, you also need .Foo instead, especially on 0.7.
Yep, it’s the hygiene. It was never quite clear to me what the point of esc was, so this is what it primarily does? It clears out these kind of prefixes to the namespace?
I’m not entirely sure I understand what you mean by using .Foo. Do you mean import .Foo? Since the actual command that is used is import and not using. Using is only for the input to the macro.
It would be good if the @force command also works within other modules, not just in Main, so would . affect its ability to work inside modules, is that just forFoo’s that are defined in Main?
How can the name of the module which made the call to the macro be determined?
The . (or in general support for other using syntax) is needed to import anything that’s not a package. I think the document should contain description of all of them. (On phone, had to search/paste doc).
I do mean using .Foo, which should be then translated to import .Foo by the macro.
Hygiene is just making sure the code is evaluated in the right scope, i.e. exactly the problem you were facing) esc is one of the tools provided for this and it makes sure code is evaluated in caller’s (user) scope rather than callee’s (macro
) scope.
Got it all sorted out. Using $(esc(:eval)) did the trick.
macro force(mod)
mod.head ∉ [:using,:toplevel] && throw(error("$mod not a using statement"))
pkgs = mod.head ≠ :using ? mod.args : [mod]
out = []
for p ∈ pkgs
m = p.args[end]
s = :([Expr(:import,Symbol($(string(m))),j) for j ∈ names($(esc(m)))])
push!(out,Expr(:import,p.args...),:($(esc(:eval))(Expr(:toplevel,$s...))))
end
return Expr(:block,out...)
end
Now the @force macro works in modules, in REPL, in Main, and works as a standalone package.
It also supports the . syntax and other namespaces, I believe.
What do you think about the following architecture:
Functions will be named meta containers bound to a module, it will contain all methods (functions with the same name
but different signature in the arguments) defined inside the module and a list of all modules that exported the same function name and were imported into the current module using the keyword using … usually in this case Julia complains about
both modules exporting the same name and that use needs to be qualified.
Instead whenever the function is parsed/compiled it should infer the most concrete match for the argument type
from the local method table and use that if there is one, if not it should look in all the modules listed as exporting
that function name and try and find a match in their local method table.
If there is more than one match only then you get an error “Modules $A and $B both export function $name , which satisfies given $args … use case must be qualified”
There can be a keyword or macro to give precedence for a Module: when searching for a method if there are two matches one from a module without precedence and one with, then there is no conflict and no error and the module which was imported with precedence get dispatched.
Thats it, I think it solves all unnaturalness of importing functions from Base and extending them.
it also prevents the following abominations:
module SelfDestruct
import Base.+
(+)(s::Int64,t::Int64) = "muhahahaha"
end
and:
module A
import Base.+
(+)(s::String,t::String) = s*t
end
module B
const C = "hello " + "world"
end
importing module B can be done only if module A was imported first.
In other words this change in architecture increases the encapsulation of modules while preserving multiple dispatch in a natural way without needing the concept of importing a function and extending a function.