[Haskell-cafe] Are explicit exports and local imports desirable in a production application?

Ignat Insarov kindaro at gmail.com
Wed Sep 16 17:21:13 UTC 2020

I apologize if this topic has already been clarified elsewhere —
please refer me.

The present message can also be seen _(and answered)_ on the Haskell
Discourse board.[1]

To clarify, an explicit export:

    module X (…) where …

An explicit import:

    import X (…)

## I would like to question the desirability of explicit exports and
local imports in a production application.

I want to make two cases:

* That explicit exports are never necessary. Moreover, that explicit
exports are harmful as they make checking of non-exported definitions
* That explicit imports are not necessary when importing a module from
the same package.

By default, I make an easy claim that none are desirable since they
incur mindless typing effort and thereby unfairly tax the programmer.
Let us then consider the justifications for their use despite that.

### Explicit exports.

When there are no explicit exports, everything is exported from a
module. Recall a case when this is disadvantageous: abstract types.
The values of abstract types must only be obtained via smart
constructors. So their definitions should not be made available.
Explicit exports may provide that.

However, there is another practice: putting dangerous definitions of
the supposedly abstract type `X` into a module `X.Internal`, then
importing them to module `X` and defining smart constructors there. By
default, a module only exports the definitions defined in itself —
there are no re-exports. So, a user importing `X` cannot invoke unsafe
constructors from `X.Internal`, and they know better than to import
the dangerous module directly.

In the former case above, the internal definitions remain out of reach
for a test suite. In the latter case, they may be freely checked as

### Explicit imports.

Recall the scenario in which explicit imports are useful.

* A module `"p" X` from package `p` imports a module `"q" Y` from
package `q v0.0.0`.
* The package `q` bumps the third version number and exports a
definition `Y.d` which name coincides with an already defined
definition `X.d`.

What happens?

  * If imports are explicit, nothing happens. The maintainers of `p`
are safe if they set a restriction as weak as `q ^>= 0.0`.

  * If imports are implicit, there would be a clash of names if `p` is
built against `q v0.0.1`, but the build would pass if it is built
against a less recent version `q v0.0.0.1`.

    The maintainers of `p` might not even notice that the package does
not build in some cases. When they do, they would have to set a
restriction `q ^>= 0.0.0`

But surely there is only one version to build against if the two
modules reside in the same package. So the overlapping names will
necessarily result in an error that would immediately be rectified. It
is no different from any other compile time error.

## Are my considerations fair?

[1]: https://discourse.haskell.org/t/are-explicit-exports-and-local-imports-desirable-in-a-production-application/1411

More information about the Haskell-Cafe mailing list