I have a package, which imports a couple of package, as well as a couple of extra functions from these. I am trying to figure out whether there is some “rule” for whether to use
using Package: function
or
import Package: function
From looking around, my impression is that I should use import Package: function if I want to overload function (i.e. create a dispatch for some structure that my package creates). If I only intend to use the function, I should instead use using Package: function.
Got it, and if I do not plan on extending, there is no reason to use import instead of using?
I am trying to figure out which to use in my package, and if this is the only difference I’d rather stick to using package: function for those I do not plan on extending (also a nice way to mark which functions I plan to do what with)
As far as I can tell, the other user facing functionality is exactly the same between using Statistics: cor and import Statistics: cor. So yes, it’s a nice way to denote which ones you plan to extend.
There’s also a minor issue where there used to a bug where using Statistics: cor would cause cor not to tab-complete properly in the REPL, while import Statistics: cor would. But I think that was fixed recently.
A nice way to have such decisions made for you is to use a style guide and the associated JuliaFormatter.jl config. I usually stick to the BlueStyle, which pretty much says
Note that ExplicitImports.jl can now help you track down lonely using MyPackage statements and replace them.
Mostly agreed, but it’s not a clear cut win when the package name is very long, and it doesn’t work for macros.
No way! I’m not going to repeat myself just because an opinionated style guide thinks import is evil. (I would be happy to using MyPackage: . though if that was implemented.)
The reason why import Blah: foo is usually discouraged is that forcing qualification gives you added protection, albeit at runtime, from silently mixing up two same names intended to be from different modules. Consider one scenario where you start writing some other included file down the line and you forgot you imported foo way back, so you start making a bunch of methods for what you think is a new function foo to do something else. At runtime, those methods are added to the imported function foo instead, easily breaking Blah by replacing methods or affecting dispatch.
Of course the reasonable counterargument is that you should keep all your imports in one place and check the names there regularly to avoid accidental name reuse. After all, variable accesses and function calls aren’t often qualified, so you’ll have to check the list anyway to distinguish the module’s names from the imports. This incidentally is why I never bought the other argument that qualification is important for distinguishing imports at first glance; if it were so important we’d be qualifying everything like in Python.
Now consider the reverse scenario where you decide to import a new package’s function import Blah: foo at the start but you forgot you had already made an internal function foo somewhere in a buried included file. Now you’ll have to check ALL names in ALL files to avoid name reuse. A linter could do this automatically, but it’s nice if the base language also warns or stops you at runtime before anything starts running for days. This runtime protection is already there for reassignment, even for import, so it’s nice if that were consistent for adding methods:
julia> module A; x = 1; foo() = 0 end
Main.A
julia> import .A: x, foo
julia> x = 2
ERROR: cannot assign a value to imported variable Main.x
Stacktrace:
[1] top-level scope
@ REPL[3]:1
julia> A.x = 2 # reassignment needs qualifying
2
julia> foo(x) = 1 # adding method does not need qualifying
foo (generic function with 2 methods)
That’s not to say import Blah: foo should never be used. A small enough module won’t span enough files to run into such issues, and base Julia does import Base: ____ a lot because those symbols are more readily recognized anyway. Macros don’t have many argument types worth dispatching over, but when they do, module qualification currently does not work so you need import Blah: @foo. That’s not so bad though because extending macros from other packages is type piracy and should be avoided.
Worth noting here is that package extensions have added the pattern of defining an empty macro in the base package and extending it with a method in the extension module.
I’m not a fan of functions or macros that do nothing until a separate package is imported, but it’s definitely a lot more maintainable for the names to unconditionally exist and belong to 1 package rather than try to make them show up only when an extension is loaded.
Is there any semantic difference between using Base: Base; const B = Base and import Base as B? It seems like splitting hairs to argue that this is a bad use of import, but I also would’ve preferred using Base as B for this
E- ah, there is using Base: Base as B if we want to duck import usage
A new variable is also semantically different from an imported and possibly renamed variable, though not practically if const.
A possible reason for why the import system is so complicated is that renaming wasn’t present for all of v1 (looking it up it seems to be 1.7, was it really that late?). With renames to a couple characters, even qualifying imported modules at accesses and function calls is pretty smooth in practice. I’d still rather import the names to occupy the name in the module and stop assignments of a new one, and might as well use it without qualification sometimes at that point.