Advanced submodule: Pattern or Anti-pattern?

The package in question has a handful (about 10) simple ‘main API’ functions. Most of those functions follow the pattern of examining the user input a bit, then selecting some combination of more generic functions to call.

Using those more generic functions directly the package is capable of a bit more than what the simple ‘main API’ functions expose but they are more verbose and difficult to use correctly. At least in my mind there is a clear distinction between the simple, the generic and the internals.

I suppose one way is to just export the generic stuff as well, but it is a significantly larger set than the set of simple functions. It is also significantly smaller than the set of internal stuff.

One thought that popped to my mind was to have an Advanced submodule which exports the generic stuff. Putting the generic in a separate package is another option, but that would make the main package look a bit silly and I also suck at naming packages. It is also a bit of a refactoring effort given that the package exists already.

I know there is no definitive answer here, but I’m looking for some opinions and consequences might not be trivial to help guide my decision.

Plots does something like that, for example:

using Plots
import Plots.Measures: mm # to define margins, for example
plot(rand(10),margin=10mm)
1 Like

I usually like that pattern. e.g

Foo.LowLevel
Foo.Hazmat
1 Like

I do something like this with Elfel.jl:
image

1 Like

Thanks, I’ll give it a shot then.

I suppose one drawback is that one must step the breaking rev whenever Advanced breaks which means that even users who don’t need it must step the compat bounds.

I suppose one can also declare that Adanced is not part of the public API but I think that is worse as it would force anyone using it to set very strict compat bounds whereas the former can be handled e.g. by communicating in the release notes (and is kinda automatically solved by CompatHelper and CI).

Another option is to have a FooCore.jl with the advanced API and a Foo.jl with the friendly API, upgraded separately and depending on FooCore.jl.

Now that I think of it, I don’t really see any downsides to this and it helps keep the boundaries clear.

2 Likes

Yeah, that is probably the best way all in all.

In this case the only drawback would be the effort to separate the code into two packages as the package already exists.