11
votes

So I read the documentation for using and import in Julia. However what this does not tell me is how I should be using these two statements in practice (and, given the lack of orthogonality, this is not too easy).

Case in point: let's put the following trivial code in "myfile.jl":

module MyModule
f() = 1
export f
end
import .MyModule # or: using .MyModule
  • if I use import on the last line, then f is not exported to Main namespace. However, when I change "myfile.jl" (e.g. modifying the return value of f) and then re-include it, the function is replaced (this is the behaviour I wish for). (Note that I could explicitly import .MyModule: f, but this introduces unneeded redundancy; also, the real-life case will involve a long list of functions with long names. OK, I might also write a macro that uses names(Main.MyModule), but I somehow feel that this ought to be simpler.)

  • if I replace import by using, then this is reversed: f is now exported, but changing anything in the module now requires re-starting the Julia interpreter.

  • using both import and using exports only the first version of f() to the main namespace: when I update the code, only the first return value is used.

So my question is not about the behaviour of both statements import and using, which are documented (if not explained) in the linked page, but about the intent behind these. Why two statements when one would be enough? Why does one of these ignore all export directives? In which case am I supposed, in practice, to use each statement?

(version is 1.1.0. Also, this runs on a system without easy Pkg access, so I did not try Revise yet.)

2

2 Answers

10
votes

The convention in Julia is to use using PackageName unless you have some specific reason to do otherwise.

This language design decision is motivated simply by users' comfort. Julia is using multiple dispatch and typically packages export functions that operate on typed arguments (those are however usually abstract types so functions functionality is not limited).

With this design all functions and methods implementing them can be in the same namespace since they differ on parameter types that are specific for their packages. Hence, a scenario where using brings a package to the namespace that overrides existing method implementations are very rare (and then Julia reports a warning). This process is additionally controlled by package authors through using the export keyword.

I think that it looks strange to you because you are accustomed to languages like Python. For an example numpy has the exactly the same function names as the default functions and there is no good way to name them differently (e.g. try to think what would be a good alternative for a function named sin :-) ). Hence, in Python you always need to import functions into their own namespaces perhaps with an alias such as import numpy as np. After having several years of experience with both languages I would say that the way Julia does it is natural and the Pytonish way is strange :-)

Regarding import and possibilities of bringing single functions from a package to your namespace, I see it as workarounds for situations where a name clash occurs. Also you need to explicitly do that if you want to add new method implementations to some other package (like defining a new meaning for the + operator). Other than that you just type using and never look back.

3
votes

One thing to add, in addition to Przemyslaw Szufel's answer. When you are extending a method, it's possible to do that without using import for the function that you are extending. For example, if I want to extend f from the module Foo, I can do using Foo and then define a new method for Foo.f. Here's a complete example:

module Foo
    struct A
        x::Int
    end

    f(a::A) = a.x + 1

    export A, f
end

using .Foo

struct B
    x::Int
end

Foo.f(b::B) = b.x + 2

The advantage of this approach is that when you are reading the code it is easier to see the origin of the function that you are extending.