Possibility of `local import` statements in future?

Alright, the @force using ModuleName macro exists now in a new package ForceImport

It works for modules that are in packages, otherwise it can’t find the source code.

The good thing is it gives warnings when it overwrites methods, so you have record of what happened.

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.

And before you ask, I mean to import the module first, not using.

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?

i.e.

Then this macro turns out very simple

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.

1 Like

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.

2 Likes

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.

It is a good idea to use @macroexpand to see what the macro expands to when debugging a macro.

  1. As a syntax, you need using .Foo.
  2. And then you need to fix your handling of using parameter to make sure you support non-trivial import statements.

This is what it expands to

quote 
    import Foo
    (ForceImport.eval)((ForceImport.Expr)(:toplevel, [(ForceImport.Expr)(:import, (ForceImport.Symbol)("Foo"), j) for j = (ForceImport.names)(ForceImport.Foo)]...))
end

The problem is that it prefixes everything with ForceImport, like ForceImport.names(ForceImport.Foo)

Is there a way to determine which module the call came from?

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.

1 Like

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.

What if I need to use eval inside of an expression returned from a macro, then how do I specify the module?

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.

1 Like

eval($mod, exp) is slightly better, mod is current_module() on 0.6 and __module__ on 0.7