On Tuesday, January 16, 2024 6:19:59 AM MST Orfeo via Digitalmars-d-learn 
wrote:
> I found myself a bit perplexed when it comes to the usage of
> "nested imports" and selective imports. It seems that prominent D
> programmers have varied opinions on the matter. I would love to
> hear your insights and experiences on this topic.
>
> Here's a quick summary of what I've come across from three
> influential D programmers:
>
> - Adam Ruppe: In his blog post titled [D's selective imports have
> effects you may not
> want](http://dpldocs.info/this-week-in-arsd/Blog.Posted_2023_11_06.html)
> have effects you may not want, Adam advises against the use of selective
> imports. He highlights potential unwanted side effects and suggests caution
> when employing them.
>
> - Atila Neves: At DConf 2023, Atila Neves recommended the use of
> nested imports. He argues that nested imports can make
> refactoring easier and help in assessing the dependencies a
> function has.
>
> - Rober Schadek: Also at DConf 2023, Rober Schadek discouraged
> the use of nested imports, taking a stance different from Atila
> Neves.
>
> Now, the big question is: What's your preferred approach?

When local imports were introduced, they were pushed as best practice (in
part, because Andrei is a big fan of them), and I think that for the most
part, they still are, but there's definitely going to be some disagreement
on it.

The benefits of local imports have to do with encapsulation. When you
localize an import as much as possible, you make it clear which parts of the
code are using that import. That makes it easier to see which imports are
used by a section of code and where symbols are coming from. It also makes
it much easier to refactor imports, because you can see what's using the
import and see whether it's still needed, whereas if an import is put at the
top of a module, it'll probably sit there forever. Tools for removing unused
imports would help with that problem, but having the imports be local still
helps you reason about the imports for a section of code (and to an extent
removes the need for such a tool). And of course, if all of the imports that
a function uses are within its body, then removing the function removes all
of those imports without you having to even spend the time to figure out
what they were.

Arguably an even bigger benefit of local imports comes from conditional
compilation. When you have a template, static if block, or version statement
in your code, the code inside of it may or may not be compiled into your
program (depending on what your code is doing). So, by putting the imports
used within that code inside of that code, you can avoid having them be
imported at all if that code isn't actually compiled in (which is
particularly critical in the case of version statements that could use
platform-specific modules but helps with avoiding unnecessary compilation in
general).

The downside of course is that you then have import statements throughout
your code, and they're often going to be duplicated throughout a module (or
even within a function if you localize them far enough), because separate
parts of the code then need their own local imports. So, some folks think
that it's just simpler to throw the imports at the top of the module (and
particularly for small programs, it probably is).

Another issue is that local imports don't really work for stuff in function
signatures. For member functions, you can localize imports to the class or
struct, but for module-level imports, they have to go at the module level,
so they're not local. There was talk of adding a language feature to fix
that, but it never happened. However, we did at some point get a template to
do it for you. object.d contains imported, which allows you to do stuff like

auto foo(imported!"std.datetime".SysTime st) {...}

I'm not sure how much it's really used though. I suspect that it's ugly
enough that it's not used much, and I don't know if it's even very well
known at this point (personally, I keep forgetting that it was added).
However, it does have the benefit of making it so that removing the
parameter or return type using that template would remove the import, just
like removing a function removes any of its local imports in the process.
So, it's arguably a good idea, but personally, I find it ugly enough that I
don't use it.

Another thing to keep in mind (though it also affects module-level imports)
is that public, package, and private affect imports - even when used with :
or with {} - so if you use an access modifier in a way that affects an
import (e.g. if you put public: at the top of your class and then have
imports right under it), you can accidentally end up with imports that
aren't private. If you're in the habit of just slapping all of your imports
at the top of the module, then this is unlikely to be an issue, but if you
put them throughout the module (and not just within functions), then it
could bite you. So, you do need to be careful about where you put imports
that aren't at the top of a module and which aren't within a function.

As for selective imports, their utility is more debatable. As Adam pointed
out in his blog post, there are subtleties that can cause confusion with
them, but for better or worse, they are usually touted as best practice. The
issues that Adam described mostly have to do with selective imports of
functions (though they can sometimes cause problems with other types of
symbols), and you don't usually need to have selective imports of functions
outside of other functions. If you're going to go to the trouble of using
selective imports, then you're almost certainly also using local imports,
and if you're calling a function, it's almost always inside of another
function (though occasionally, you might do something like initialize an
enum with a function call outside of a function), so when you selectively
import a function, it's almost always with a local import, and that
generally isn't a problem in my experience.

The big benefit from selective imports is that you can see where symbols
come from, whereas if you just have naked imports, you have to be familiar
with those modules to know what comes from where (which you often are, but
it can definitely help when reading unfamiliar code). So, they can
definitely help with understanding code - but on the other hand, you do then
have to change them pretty much every time that you change which symbols
you're using, which is a maintenance cost and can get pretty annoying (the
same happens to an extent with local imports, but since you often use
several symbols from a module, you don't have to change local imports as
often as you have to change selective imports). So, the cost-benefit
analysis for selective imports is far less clear than it is with local
imports, and I think that you're much more likely to see disagreement over
whether they should be used. Often with the code that I've worked on,
selective imports get used, but they then get dropped if you end up with
more than a handful of symbols from that module are being used. I'm also
much less likely to use selective imports outside of functions.

IIRC, at one point, it was thought by some that selective imports would
allow the compiler to reduce compilation times (by not actually compiling
the symbols that aren't used), but with how D's compilation model works,
selective imports don't work that way, and I don't think that they actually
can work that way (particularly when compiling modules individually is a
thing).

Personally, I would say that you probably should be using local imports as
much as is reasonable, though where that line is obviously depends on you
and your pain threshhold for code duplication. Initially, I was resistant to
using them, but ultimately, I've found them to be beneficial enough that I
use them quite heavily now (though I haven't gone so far as to use the
imported template). I would also suggest that you use selective imports as
much as reasonable, since I've found that they make reasoning about the code
easier, because they make it clear where the symbols are coming from. You do
obviously need to be aware that you could run into problems with them
depending on how you use them, but I don't even recall the last time that I
had a problem due to a selective import. Regardless, obviously, you are
going to have to decide what works for you, and that could change over time.
Both features can definitely help with code encapsulation and with
understanding where the symbols being used are actually coming from, but the
cost-benefit analysis is likely to differ based on how you think and
function.

- Jonathan M Davis



Reply via email to