Why does code in a package use "using" and "import"?

Hi, In looking at how Julia packages are implemented, I see a common idiom that I don’t quite understand. For example, if you look at YaoBase/src/YaoBase.jl at https://github.com/QuantumBFS/YaoBase.jl/blob/master/src/YaoBase.jl you see

using YaoAPI

import YaoAPI: isunitary, isreflexive, iscommute,
    AbstractRegister, AdjointRegister, AbstractBlock,
    PostProcess,
    NotImplementedError, LocationConflictError, QubitMismatchError,
    instruct!, focus!, relax!, nqubits, nremain, nactive, nbatch,
    viewbatch, addbits!, insert_qubits!, measure, measure!,
    occupied_locs, invorder!, partial_tr, select!, ρ, reorder!

Looking at the Julia docs at https://docs.julialang.org/en/v1/manual/modules/#Summary-of-module-usage , I see that the using YaoAPI makes all functions accessible (even non-exported ones with YaoAPI.fcnName) and also makes them extensible. As far as I know, all that the long import statement does is allows for use of non-exported functions from YaoAPI without the YaoAPI. prefix. So it’s just convenience.

Is that really right or is there more going on here? I’ve started writing my own packages, so I’m wondering what I’m missing, if anything.

Thanks in advance for explanations! – Adam

1 Like

Sometimes, it is nice to explicitly specify which function has been imported from a package. It avoids the reader to actually search in the code or other packages where the function is defined.
Maybe, that’s the reason here.

Otherwise, it might be for overloading functions.

2 Likes

Thanks! That makes sense. – Adam

Isn’t it that first, they load all exported functions, and then afterwards, some of the non-exported ones too, for convenience?

I think you misunderstand, you need either

import Foo: f
f(::SomeType) = ...

or

import Foo # only brings Foo into the namespace, without its exports
Foo.f(::SomeType) = ...

to define methods for Foo.f.

1 Like

It’s not quite true. It may look weird, but using <Module> indeed make all functions extensible: https://docs.julialang.org/en/v1.4/manual/modules/#Summary-of-module-usage-1

You can always define methods if you prefix with the module name — using Foo does not make this happen, it just brings Foo into the namespace which allows you do do this.

Yes, you are right and docs are misleading. It looks like it should be something like this

In this module we export the x and y functions (with the keyword export ), and also have the non-exported function p. There are several different ways to load the Module and its inner functions into the current workspace. Take into account, that irrelevant of the way how you load the Module, MyModule.x , MyModule.y and MyModule.p are always brought into the scope and you can always extend MyModule.x , MyModule.y and MyModule.p

Import Command What is brought into scope Available for method extension
using MyModule All export ed names ( x and y )
using MyModule: x, p x and p
import MyModule
import MyModule.x, MyModule.p x and p x and p
import MyModule: x, p x and p x and p
4 Likes

I think the docs are OK, but this version is not correct, eg simply using MyModule: x, p will not work as it does not bring MyModule into scope.

1 Like

Aha, it all clicks together now.
I was experimenting with modules locally, and include them with include command. In this case MyModule was loaded to scope and I have had MyModule.x etc with all the consequences.

Now, if MyModule lives in a separate package, the situation is somewhat different and the docs table is valid. And the difference between using MyModule and using MyModule: x exists. Here is an example

julia> using DecisionTree

julia> methods(build_tree)
# 12 methods for generic function "build_tree":

julia> build_tree(x) = x
ERROR: error in method definition: function DecisionTree.build_tree must be explicitly imp
orted to be extended

julia> DecisionTree.build_tree(x) = x

julia> methods(build_tree)
# 13 methods for generic function "build_tree":

So, build_tree can not be extended on it’s own, but it can be extended with module name.

But if only one name is imported with the using then it can’t be extended at all

julia> using DecisionTree: build_tree

julia> build_tree(x) = x
ERROR: error in method definition: function DecisionTree.build_tree must be explicitly imp
orted to be extended
Stacktrace:
 [1] top-level scope at none:0
 [2] top-level scope at REPL[2]:1

julia> DecisionTree.build_tree(x) = x
ERROR: UndefVarError: DecisionTree not defined
Stacktrace:
 [1] top-level scope at REPL[3]:1

To clarify, what I wanted to say in initial post is that both constructions are ok

import Foo # only brings Foo into the namespace, without its exports
Foo.f(::SomeType) = ...
using Foo # brings Foo into the namespace and its exports
Foo.f(::SomeType) = ...

Here is short summary of the discussion:

using Foo
import Foo: bar, baz

has the following effects:

  1. You can access and extend all Foo methods using Foo. prefix (this one is coming from using Foo)
  2. You can use all Foo exported methods without using Foo. prefix (also from using Foo)
  3. You can use bar and baz methods without using Foo. prefix (from import Foo: bar, baz)
  4. You can extend bar and baz methods without using Foo. prefix (from import Foo: bar, baz)

Also, you may try to compare it with different combinations of using/import

using Foo
using Foo: bar, baz

Comparing to the previous case you have the following:

  1. You can use all methods in the same way as in previous case, i.e. Foo.foo1, Foo.bar, bar, baz and so on.
  2. You can extend all methods using Foo. prefix as in previous case.
  3. You can’t extend bar, baz without using Foo. prefix <- this is the main difference.
2 Likes

Not telling anything new, but this way of explaining it helps me understand better:

  • About importing and using modules:

    • using Foo brings the module Foo and its exported objects into scope.
    • import Foo brings the module Foo into scope, and nothing else.
  • About importing and using objects from a module:

    • using Foo: x, y brings x, y from module Foo into scope, but not the module Foo itself or other objects from it.
    • import Foo: x, y (or import Foo.x, Foo.y) does the same, but if x or y are functions, they’ll be available for method extension in the current scope.
  • Additionally, all functions from a given module are always available for method extension in their module’s scope. So, if the module Foo has been brought into scope of module Bar (with using or import), its functions can be extended in the code of Bar referring to them as Foo.x, etc.

13 Likes

This is how I think about this, too. I think this part of the manual should be revised accordingly.

5 Likes

Thanks @heliosdrm! That’s a nice succinct summary of usage and import.