Is an explicit "export" a good thing?

Thanks for writing it up. It is great that you document your intended interface, and provide examples.

I still think that for one-off usage, it is best to just import explicitly from packages that choose not to export their API. However, if a user is consistently using the same subset of some API, creating some module that imports and reexports could be worthwhile, especially if we are talking about a lot of symbols.

The downside is the extra administrivia of creating a local module. I like to keep my code (even scripts) independent of the current working directory, so I try not to use include in scripts, or alternatively cd to some path I can calculate (eg from @__DIR__ in some module file). Ideally, the interface module which reexports would be available in LOAD_PATH.

Finally, if you stick to this interface, consider writing a macro that imports and reexports, along the lines of
https://github.com/simonster/Reexport.jl
but just for a subset of symbols (explicitly specified).

Thanks for starting this topic. Even if I don’t grasp the motivation for this interface module (possibly because I have not encountered the use case), it made me think about my own export choices.

If I understand you correctly, your workflow consists of writing lots of short scripts? If so, it will be quite different from mine. I like to put my code in modules so the workspace can be kept clean and manageable.

You mentioned LOAD_PATH: I believe I saw somewhere recently that the new package manager would get rid of this entirely.

And, thanks for the pointer. Reexport looks interesting.

Can you not use a macro to essentially do the same thing? The macro can be something like FinEtools.@import LinearElasticity Meshing which would then import all the parts of the package related to linear elasticity and meshing for example. These could be the API identifiers from one or more submodules of FinEtools. Would that serve the purpose without introducing a new API module?

Sorry, I’m not following: which part of the process would this macro facilitate? Are you referring to the use-case package?

Yes to essentially replace the use-case package. So as a user, I can still tell FinEtools which parts I am interested in without adding them to another myFinEtools package. Not sure if that will serve the purpose in your mind?

I see (I think). How would you organize the exports in the FinEtools package into groups then? One thing I was not happy about was that the exports hhave been scattered across the ~50 modules. It seemed to me much cleaner to organize the exports so that all the export statements are in a single file (the top level package file). For the macro you propose, these exports would have to be organized into groups… Not sure how to do that.

Hmm, not really sure I haven’t dealt with submodules before, but if I think of something I will let you know.

Something along these lines perhaps.

module Outer
module Inner
f() = 1
g() = 2
end

import .Inner: f, g
macro simport(x...)
    expr = Expr(:toplevel)
    for _x in x
        if _x == :(Funcs)
            push!(expr.args, Expr(:import, :Outer, :f))
            push!(expr.args, Expr(:import, :Outer, :g))
        end
    end
    return esc(expr)
end
end

Outer.@simport Funcs

f()
g()

Notice all the symbols can be imported first into the top level module and then grouped together in the macro, all in one place. Then I can tell the Outer module what is that I want, and the macro will take care of the shopping for me.

Or more neatly:

module Outer
module Inner
f() = 1
g() = 2
end

import .Inner: f, g
groups = Dict(:Funcs => [:f, :g])

macro simport(x...)
    expr = Expr(:toplevel)
    for _x in x
        if haskey(groups, _x)
            for _f in groups[_x]
                push!(expr.args, Expr(:import, :Outer, _f))
            end
        end
    end
    return esc(expr)
end
end

Only 2 lines have to be modified to include the functions and groups from your 50 submodules instead of the single Inner module.

Cool! I will ponder this a bit… Thanks.

Petr

This is a neat trick. I like it.

It does go quite a bit beyond what I intended. My goals were:

  1. I wanted to clean up the public interface of my FinEtools package. I managed that by concentrating all exports into a single file, which is a distinct improvement on the previous state where exports were scattered across 50 different files.

  2. I wanted to make it POSSIBLE for USERS to manage the public interface of the FinEtools package. By that I mean include in the interface even functions which the developer did not make part of the interface originally, and exclude functions which are part of the default public interface. This new public interface should be as expressive and as easy to use as the developer-provided one.

Honestly, I don’t see a huge demand for users to manage the public interface, most will probably just do using FinEtools. But it should be possible by design. The Julia language certainly provides the facilities to make it happen with targeted using and import.

For fine grained control of what’s brought to namespace, I think import FinEtools: f, g is good enough. For coarse grained control, using a sub-module which exports related API identifiers is probably the standard way, but you will have to export at the sub-module which you seem to not want. So a middle way solution that allows exporting at the top level module only while providing coarse grained control is probably the macro above. You also get the added possibility of grouping functionalities from different sub-modules if that may ever be useful (I doubt it). So I guess that’s my point of view on this. It would be interesting to see what you ended up choosing! But I think making a new package altogether to manage imports is a little tedious and probably an overkill compared to the above methods.

This new package is not a necessity. I just stated that as a possibility. In any case, that would be under the user’s control. The user would decide how to structure his or her code. My point was that if the user wished to have a public interface to FinEtools different from the developer-provided one, now there is an easy path to that. And, with your macro it should be possible to use a fairly coarse-grained control of the imports, if so desired. (I think it could be very useful.)