Keywords using and import: are they really needed both?

A longish discussion has been going back and forth what to do about these two keywords.

Consider the following code:

module Foo
export nice, DOG
struct Dog end      # singleton type, not exported
const DOG = Dog()   # named instance, exported
nice(x) = "nice $x" # function, exported
notnice(x) = "not nice $x" # function, not exported
end

This table shows the equivalents:

Purpose Keyword using Keyword import
Get the module name: using .Foo: Foo import .Foo
Get all exported names: using .Foo NA
Get only selected names: using .Foo: nice import .Foo: nice
Get access to qualified names only: using .Foo: Foo import .Foo
Get access to add a method: using .Foo: Foo; Foo.nice(i::Int) = i import .Foo; Foo.nice(d::Float64) = d
Get access to add a method without needing a qualified name: NA import .Foo: notnice; notnice(d::Float64) = -d

Question: are these differences really worth it to have two keywords?

1 Like
2 Likes

I’m not sure if I read the issues completely right, but assuming “soft binding” there means you need to qualify the module to extend a function while “hard binding” means you just need the function name, I think I basically arrived at the suggested 2.0 import by doing using with no list for implicit exports and import with a list of explicit names, and only the latter in source code.

I never liked any justification for “soft bindings” that need module qualification when extending a function but not anytime else. Even in an interactive context where it’s convenient not to write out explicit lists, it’s not so convenient when I forget I needed to qualify the module, and I either run into an error or irreversibly make a new function shadowing the imported name.

Erroring case, also works for non-functions.
julia> module B
        export g
        g(x) = 1
       end; using .B

julia> f() = g(1)
f (generic function with 1 method)

julia> f()
1

julia> g(x) = 10
ERROR: invalid method definition in Main: function B.g must be explicitly imported to be extended
Irreversible shadowing case, also works for non-functions.
julia> module B
        export g
        g(x) = 1
       end; using .B

julia> f() = g(1)
f (generic function with 1 method)

julia> g(x) = 10
g (generic function with 1 method)

julia> f()
10

julia> B.g === g
false
Mixing qualified modules and explicit renaming also gets inconsistent.
julia> module B
        g(x) = 1
       end; using .B: g as gB

julia> gB(1)
1

julia> gB() = 0
ERROR: invalid method definition in Main: function Main.gB must be explicitly imported to be extended
...
julia> B.gB() = 0
ERROR: UndefVarError: `gB` not defined
...
julia> B.g() = 0

As far as I’m concerned, I should pick one of B.g, g, or gB to write when they would all mean the same thing. I would like the implication that B.g !== g when both are written. A currently missing piece to do that fully is not being able to reassign explicitly imported names, as the recent setglobal! could only work with a qualified module. I would hope a “hard binding” doesn’t need a qualified module to lower to its setglobal! call or an equivalent.

A nice thing about ... being used to denote implicit thus soft bindings in import Foo: ... is that it’s simpler to discourage than using Foo, which on the surface looks like import’s equal. That’s evidently confusing enough to beginners who already often struggle to distinguish imports and include.