Difference between revisions of "Import modules properly"

From HaskellWiki
Jump to navigation Jump to search
m (→‎Introduction: minor typos)
 
(11 intermediate revisions by 2 users not shown)
Line 1: Line 1:
  +
== Introduction ==
{{essay}}
 
   
Haskell has a lot of variants of importing identifiers from other modules.
+
Haskell has a lot of variants of [[Import|importing]] identifiers from other modules.
 
However not all of them are as comfortable as they seem to be at the first glance.
 
However not all of them are as comfortable as they seem to be at the first glance.
 
We recommend to focus on the following two forms of import:
 
We recommend to focus on the following two forms of import:
 
<haskell>
 
<haskell>
 
import qualified Very.Special.Module as VSM
 
import qualified Very.Special.Module as VSM
import Another.Important.Module (printf)
+
import Another.Important.Module (printf, (<|>), )
 
</haskell>
 
</haskell>
 
instead of
 
instead of
 
<haskell>
 
<haskell>
 
import Very.Special.Module
 
import Very.Special.Module
import Another.Important.Module hiding (open, close)
+
import Another.Important.Module hiding (open, close, )
 
</haskell>
 
</haskell>
   
  +
There are four different kind of reasons for this.
Stylistic reason:
 
If you read <hask>printf</hask> or <hask>VSM.open</hask> in the program you can find out easily where the identifier comes from.
 
In the second case you don't know if these identifiers are from <hask>Very.Special.Module</hask>, <hask>Another.Important.Module</hask> or even other modules.
 
Mind you that grep won't help, because <hask>Very.Special.Module</hask> and <hask>Another.Important.Module</hask> might just re-export other modules.
 
   
  +
* '''Style:''' If you read <hask>printf</hask>, <hask><|></hask> or <hask>VSM.open</hask> in the program you can find out easily where the identifier comes from. In the second case you don't know if these identifiers are from <hask>Very.Special.Module</hask>, <hask>Another.Important.Module</hask> or even other modules. Mind you that grep won't help, because <hask>Very.Special.Module</hask> and <hask>Another.Important.Module</hask> might just re-export other modules. You might guess the origin of <hask>printf</hask> according to its name, but for the infix operator <hask><|></hask> you will certainly have no idea.
Compatibility reason:
 
  +
* '''Compatibility:''' In the second case, if new identifiers are added to the imported modules they might clash with names of other modules. Thus updating imported modules may break your code. If you import a package A with version a.b.c.d that follows the [[Package versioning policy]] then within versions with the same a.b it is allowed to add identifiers. This means that if you import the suggested way, you can safely specify <code>A >= a.b.c && <a.b+1</code> in your [[Cabal]] file. Otherwise you have to choose the smaller range <code>A >= a.b.c && <a.b.c+1</code>. It may also be that <hask>Another.Important.Module.open</hask> was deprecated when you hid it, and with a module update removing that identifier, your import fails. That is, an identifier that you never needed but only annoyed you, annoys you again, when it was meant to not bother you any longer! The first variant of import does not suffer from these problems.
In the second case, if new identifiers are added to the imported modules they might clash with names of other modules.
 
  +
* '''Correctness:''' I once found a bug in the StorableVector package by converting anonymous imports to explicit imports. I found out that the function <hask>Foreign.Ptr.plusPtr</hask> was imported, although functions from this module always have to calculate with unit "element" not "byte". That is, <hask>advancePtr</hask> must be used instead. Actually, the <hask>reverse</hask> function used <hask>plusPtr</hask> and this was wrong. A misbehaviour could only be observed for sub-vectors and elements with size greater than 1 byte. The test suite did miss that.
Thus updating imported modules may break your code.
 
  +
* '''Maintenance:''' All too often I find an old module that cannot be compiled any longer since it uses identifiers that do no longer exist. If the module imports implicitly and without qualification I have little chance to find out where the identifiers originally came from, what they meant and how they must be replaced.
It may also be that <hask>Another.Important.Module.open</hask> was deprecated when you hid it,
 
and with a module update removing that identifier, your import fails.
 
That is, an identifier that you never needed but only annoyed you, annoys you again, when it was meant to not bother you any longer!
 
The first variant of import does not suffer from these problems.
 
 
Correctness reason:
 
I once found a bug in the StorableVector package by converting anonymous imports to explicit imports.
 
I found out that the function <hask>Foreign.Ptr.plusPtr</hask> was imported,
 
although functions from this module always have to calcuate with unit "element" not "byte".
 
That is, <hask>advancePtr</hask> must be used instead.
 
Actually, the <hask>reverse</hask> function used <hask>plusPtr</hask> and this was wrong.
 
A misbehaviour could only be observed for sub-vectors and elements with size greater than 1 byte.
 
The test suite did miss that.
 
   
 
== Exception from the rule ==
 
== Exception from the rule ==
   
Since the Prelude is intended to be fixed for the future,
+
Since the Prelude is intended to be fixed for the future, it should be safe to use the <hask>hiding</hask> clause when importing <hask>Prelude</hask>.
it should be safe to use the <hask>hiding</hask> clause when importing <hask>Prelude</hask>.
 
 
Actually if you do not mention Prelude it will be imported anonymously.
 
Actually if you do not mention Prelude it will be imported anonymously.
   
== Clashing of abbreviations ==
+
== Clashing of module name abbreviations ==
   
 
In Haskell it is possible to use the same abbreviation for different modules:
 
In Haskell it is possible to use the same abbreviation for different modules:
Line 51: Line 35:
 
This is discouraged for the same reasons as above:
 
This is discouraged for the same reasons as above:
   
 
* '''Style''': The identifier <hask>List.intercalate</hask> may refer to either <hask>Data.List</hask> or <hask>Data.List.Extra</hask>. The reader of that module has to check these modules in order to find it out.
Stylistic reason:
 
The identifier <hask>List.intercalate</hask> may refer to either <hask>Data.List</hask> or <hask>Data.List.Extra</hask>.
 
You have to check these modules in order to find it out.
 
   
  +
* '''Compatibility''': The function <hask>List.intercalate</hask> may be currently defined only in <hask>Data.List.Extra</hask>. However after many people found it useful, it is also added to <hask>Data.List</hask>. Then <hask>List.intercalate</hask> can no longer be resolved.
Compatibility reason:
 
The function <hask>List.intercalate</hask> may be currently defined only in <hask>Data.List.Extra</hask>.
 
However after many people found it useful, it is also added to <hask>Data.List</hask>.
 
Then <hask>List.intercalate</hask> can no longer be resolved.
 
   
== Counter-arguments ==
+
== Counter-arguments to explicit import lists ==
   
The issue of whether to use explicit import lists is not always clear-cut, however. Here are some reasons you might not want to do this:
+
The issue of whether to use explicit import lists is not always clear-cut, however.
  +
Here are some reasons you might not want to do this:
   
 
* Development is slower: almost every change is accompanied by an import list change, especially if you want to keep your code warning-clean.
 
* Development is slower: almost every change is accompanied by an import list change, especially if you want to keep your code warning-clean.
Line 68: Line 48:
 
* When working on a project with multiple developers, explicit import lists can cause spurious conflicts, since two otherwise-unrelated changes to a file may both require changes to the same import list.
 
* When working on a project with multiple developers, explicit import lists can cause spurious conflicts, since two otherwise-unrelated changes to a file may both require changes to the same import list.
   
For these reasons amongst others, the GHC project decided to drop the use of explicit import lists. We recommend using explicit import lists when importing from other packages, but not when importing modules within the same package.
+
For these reasons amongst others, the GHC project decided to drop the use of explicit import lists.
  +
We recommend using explicit import lists when importing from other packages,
  +
but not when importing modules within the same package.
  +
 
Qualified use of identifiers does not suffer from the above problems.
   
 
== See also ==
 
== See also ==
   
 
* [[Qualified names]]
 
* [[Qualified names]]
  +
  +
* {{HackagePackage|id=check-pvp}} is a program that checks for consistency between package dependencies and import style.
  +
 
{{essay}}
   
 
[[Category:Style]]
 
[[Category:Style]]

Latest revision as of 08:28, 2 May 2015

Introduction

Haskell has a lot of variants of importing identifiers from other modules. However not all of them are as comfortable as they seem to be at the first glance. We recommend to focus on the following two forms of import:

import qualified Very.Special.Module as VSM
import Another.Important.Module (printf, (<|>), )

instead of

import Very.Special.Module
import Another.Important.Module hiding (open, close, )

There are four different kind of reasons for this.

  • Style: If you read printf, <|> or VSM.open in the program you can find out easily where the identifier comes from. In the second case you don't know if these identifiers are from Very.Special.Module, Another.Important.Module or even other modules. Mind you that grep won't help, because Very.Special.Module and Another.Important.Module might just re-export other modules. You might guess the origin of printf according to its name, but for the infix operator <|> you will certainly have no idea.
  • Compatibility: In the second case, if new identifiers are added to the imported modules they might clash with names of other modules. Thus updating imported modules may break your code. If you import a package A with version a.b.c.d that follows the Package versioning policy then within versions with the same a.b it is allowed to add identifiers. This means that if you import the suggested way, you can safely specify A >= a.b.c && <a.b+1 in your Cabal file. Otherwise you have to choose the smaller range A >= a.b.c && <a.b.c+1. It may also be that Another.Important.Module.open was deprecated when you hid it, and with a module update removing that identifier, your import fails. That is, an identifier that you never needed but only annoyed you, annoys you again, when it was meant to not bother you any longer! The first variant of import does not suffer from these problems.
  • Correctness: I once found a bug in the StorableVector package by converting anonymous imports to explicit imports. I found out that the function Foreign.Ptr.plusPtr was imported, although functions from this module always have to calculate with unit "element" not "byte". That is, advancePtr must be used instead. Actually, the reverse function used plusPtr and this was wrong. A misbehaviour could only be observed for sub-vectors and elements with size greater than 1 byte. The test suite did miss that.
  • Maintenance: All too often I find an old module that cannot be compiled any longer since it uses identifiers that do no longer exist. If the module imports implicitly and without qualification I have little chance to find out where the identifiers originally came from, what they meant and how they must be replaced.

Exception from the rule

Since the Prelude is intended to be fixed for the future, it should be safe to use the hiding clause when importing Prelude. Actually if you do not mention Prelude it will be imported anonymously.

Clashing of module name abbreviations

In Haskell it is possible to use the same abbreviation for different modules:

import qualified Data.List as List
import qualified Data.List.Extra as List

This is discouraged for the same reasons as above:

  • Style: The identifier List.intercalate may refer to either Data.List or Data.List.Extra. The reader of that module has to check these modules in order to find it out.
  • Compatibility: The function List.intercalate may be currently defined only in Data.List.Extra. However after many people found it useful, it is also added to Data.List. Then List.intercalate can no longer be resolved.

Counter-arguments to explicit import lists

The issue of whether to use explicit import lists is not always clear-cut, however. Here are some reasons you might not want to do this:

  • Development is slower: almost every change is accompanied by an import list change, especially if you want to keep your code warning-clean.
  • When working on a project with multiple developers, explicit import lists can cause spurious conflicts, since two otherwise-unrelated changes to a file may both require changes to the same import list.

For these reasons amongst others, the GHC project decided to drop the use of explicit import lists. We recommend using explicit import lists when importing from other packages, but not when importing modules within the same package.

Qualified use of identifiers does not suffer from the above problems.

See also

  • check-pvp is a program that checks for consistency between package dependencies and import style.
This page addresses an aspect of Haskell style, which is to some extent a matter of taste. Just pick what you find appropriate for you and ignore the rest.