Is an explicit "export" a good thing?

I am beginning to have doubts about the wisdom of using “export” in modules. When the modules get "using"ed (could we change this keyword please to “use” instead of “using”?), for someone coming to a finished code it may be an unnecessary cognitive burden to figure out the origin of the functions coming from these modules. On the other hand, if the writer says explicitly

using MyMod: fun

the comprehension of the code is enhanced and no searching is required.

I wonder if someone wants to put a word either for or against “export”?

3 Likes

This doesn’t work with multiple methods. E.g. I can extend Base.push! for my type, never export it, but it will be automatically available for a user once he imports my module.

On the other hand, methods(push!) and @which push!(obj, x) always works and doesn’t depend on export.

1 Like

I’m also a bit on the fence on this topic. The danger of name clashes due to exports with the same name from different packages is always in the back of my mind. Packages like ForwardDiff don’t export anything, probably for this reason (it is likely that some other package could also export e.g. jacobian and derivative).

On the other hand, I, and I think many others currently implicitly treat the list of exported symbols as the ‘official’ API of a package, with anything that’s not exported subject to change without deprecation.

5 Likes

The API point is a good one. I would probably keep a list of “exported” functions in a comment at the top of documentation as the API spec.

It depends on what you’re doing. In a package, being explicit is usually better. In the REPL prototyping for a class, having to always be explicit is slow and annoying.

Good point. But the problem is that once the developer writes “export blah, blahblah”, the cat is out of the bag.

No, just use import

1 Like

Could you explain what you mean?

import will not bring the symbols exported by the package into the namespace. It will only bring the package name itself so you need to qualify with PackageName.symbol. One thing that I find a bit unfortunate is that there isn’t really any way to denote symbol as public if it is non exported. You can, as an author, work around this by something like:

module Package
   public_function(x) = x
   public_function2(x) = __Inner.f(x)

    module __Inner
        f(x) = x
        other_private(x) = x
        more_private(x) = x
    end
end

Everything in Package. namespace is now public (except __Inner).

Or you can import only symbols you want:

import PackageName: symbol1, symbol2

which should act exactly as using MyMod: fun for not exported functions as mentioned in the first post.

But one can do

using Mod: fun

to get to call fun(x) as if it was exported? No?

Btw I am not so much concerned w/ public vs private. In my (barely formed) view it seems counterproductive for READERS of code to have to figure out where functions came from after they were exported from modules and now appear in the code as if by magic.

Yes, so use import then and use the module prefix att call sites.

1 Like

Isn’t it “nicer” to use using Mod: f; f(x) rather than import Mod; Mod.f(x)? Just curious why you recommend import…

@kristoffer.carlsson, @ChrisRackauckas, @tkoolen Could one eat the cake and have it too?

Look Ma, no exports!

module mmm
function test()
    println("in test()")
end
function folly()
    println("in folly()")
end
end

Hence to use these functions one must qualify them or bring them in explicitly.

julia> include("mmm.jl")
mmm

julia> mmm.folly()
in folly()

julia> using mmm: folly

julia> folly()
in folly()

And here is the explicit API:

module mmm_API
using mmm: test, folly
export test, folly
end

which is used as

julia> include("mmm_API.jl")
mmm_API

julia> using mmm_API

julia> folly()
in folly()

So if you are inclined to make it real clear where the functions are coming from, use mmm; otherwise use the API module.

Or if you just did what I noted earlier, you’d get all of that with less work. using exported names into the namespace. import doesn’t. Problem solved without an extra module, and it’s consistent across all of Julia.

1 Like

I am sorry. I need a bit more clarity. What exactly ARE you saying?

1 Like

import mmm_API is the same as using mmm, so making the two modules is unnecessary since there’s already a way to turn off exporting if you don’t want it.

Importing exported functions with using is sometimes useful, especially for interactive environments (e.g. Jupyter, RPEL) and for development. So, I’d say :+1: to use export in some modules.

What I dislike is using using in source code because it hides the origin of names used in the code. In such a case, we can always use import and explicitly prefix names with the module name, or, explicitly import names from the module if those names are frequently used in the code.

3 Likes

The current usage of export has two (related) roles:

  1. a shorthand for documenting the API (“this is exported so you should read its documentation, this isn’t so think before you use it”),

  2. quickly importing a consistent set of nouns and verbs that was determined by the package authors to be useful, with various trade-offs in mind (eg conflicts with other packages).

There is no consistent standard, so the package authors rely on tradition, example, and their own preferences about what to export.

I think that writing good documentation and a consistent API with a few symbols that need to be imported explicitly makes no explicit exports viable. Some packages indeed do this; AD is a good example.

OTOH, some packages (and collections of packages) cultivate a consistent API that is exported, and make sure that there are no conflicts; sometimes with the use of packages with the sole purpose of defining and exporting some skeleton of a generic interface. JuliaStats is an example of this.

I think that the point you are making is valid. On the other hand, some languages treat their namespaces differently, eg in R the whole API is imported by default for libraries, so most users don’t even need to think about namespaces. For users coming from these languages, starting everything with a set of imports may be unusual, especially in interactive use.