At the last HaskellMN meetup, Danny Gratzer had a question about Python imports. Specifically, he wanted to import and re-export a constant. Here’s how it is done in Python:
# a.py: some_constant = 3.14 # b.py: from a import some_constant # c.py: from b import some_constant
Edit: He had a follow-up question.
This reminded me that I wanted to compare the import styles of Python, Haskell, and Swift. Their imports are similar, though there are a few differences to keep in mind.
Python has a few import styles:
import module1 from module2 import a, b, c from module3 import *
The first loads
module1 and brings it into scope. The second loads objects
module2 and brings them into scope. The third loads every object from
module3 and brings them into scope.
The first is useful for short names (
sys), but with module hierarchies it can be annoying. The second is my preferred style, as it forces you to name what will be used. The third is frowned upon, as you can accidentally overwrite variables if a module you import later provides an object with the same name as one you import earlier. Additionally, providing a list of what is going to be used in the file is useful from a documentation standpoint. “Explicit is better than implicit.”
Python modules are isomorphic to files. A module is either a file with that name or a folder with an
__init__.py file in it1. Submodules can be placed in the folder.
Python imports can be qualified using
from module import a as b
module, but gives it the name
b in scope.
Python imports can also happen anywhere in a file, even inside functions. This is a double-edged sword, useful for both making and breaking circular dependencies.
Haskell, too, has a few import styles.
import qualified Module1 import Module2 (a, b, c) import Module3
The first loads
Module1 and brings it into scope. The second loads objects
Module2 and brings them into scope. The third loads every (exported) object from
Module3 and brings them into scope.
These correspond loosely to the three Python examples, however, I put them in the roughly similar semantic order, rather than by ordering them by their similar syntax. Again, I prefer the middle one for similar reasons.
There is also a
hiding option, which is the inverse of of the second option option above:
import Module hiding (a, b, c)
Haskell modules are made explicit within files:
module Module where ...
The convention is to put them in the corresponding file structure, though this is not enforced programmatically.
Unlike Python imports, Haskell imports need to be at the top of a file, optionally following a module declaration.
Haskell provides the
as option, too, though only for module-level imports:
import Module as M import qualified Module as M
However, the first example here also imports everything from
Module in addition to qualifying the module-level import as
This would import everything from
Swift imports are not as diverse. They only provide equivalents for the second and third examples from above:
import var module2.a import module3
The first loads
module2 and brings it into scope. The second loads objects
module3 and brings it into scope. There doesn’t seem to be an equivalent to the
module1 example from Python and Haskell.
Compared to the
module2 examples from Python and Haskell, the example here provides two additional constraints. First, the import kind needs to be specified. In the example I gave here the import kind is
var, though it could also be
func. The other constraint is each import from a module would need to be in its own line. A more precise equivalent to the Python and Haskell examples for would be:
import var module.a import var module.b import var module.c
This is a little excessive. I think a better syntax would be something like one of these:
from module import var a, var b, var c import module (var a, var b, var c)
Like Python, Swift imports can be placed anywhere within a file.
The morality of import styles
Importing a module name is fine, though it can make some code hard to read due to the repetitive and verbose nature of including the module name for each access into the module.
Importing specific items from a module is my preferred import style, except when it isn’t idiomatic (e.g.
import sys in Python).
Importing everything from a module without naming each item is frowned upon because it doesn’t convey everything to readers of the code. Code readers like to be able to see what is in scope in order to get a feel for what kind of code will be read in a file.
Additionally, if multiple modules define items with the same name, it is possible to overwrite imports implicitly, without notifying the reader:
# a.py: x = 1 # b.py: x = 2 # c.py: from a import * from b import * print(x)
These suggestions are reflected in Python’s PEP 8 and the HaskellWiki page on “Import[ing] modules properly”. These discuss the merits of the various import styles, and they arrive at similar conclusions, though the HaskellWiki page is less axiomatic and provides more rationale for the various decisions. There is not yet a similar document for Swift, though the official blog post on Files and Initialization mentions that you “can even import modules at the bottom of the file (although that is not recommended Swift style.”
A folder with an
__init__.pyis technically a package.↩