[Haskell-cafe] Design your modules for qualified import

Johan Tibell johan.tibell at gmail.com
Thu Jun 5 11:19:28 EDT 2008


Hi!

The last two weeks I've seen two new packages that add suffixes to
identifiers instead of relying on them being imported qualified to
distinguish them from identifiers in other modules. This is not a new
thing in Haskell community and there are examples of this practice in
libraries shipped with GHC. It is also common in papers (for good
reasons as we will see later). Atomic channels and variables under
Control.Concurrent.* are examples of using a prefix as a namespacing
tool. Let me reiterate the arguments [1,2] against this practice and
also speculate why this practice is more common in Haskell than in
e.g. Python, Java or Ruby.

* Cons of including a namespace prefix/suffix in identifiers

1. From experience these prefixes/suffixes tend to be short and
non-descriptive, typically just one letter e.g. foldU or elemB. Very
frequently used functions that every Haskell programmer is likely to
use often can have shorter name as the cost of memorizing them is
amortized over all their uses. Examples of short names that most
Haskell programmers know by heart is map, fold*, etc. Given that your
library won't see the same use as these functions your functions need
more descriptive names (or in this case use a qualified import that
makes them more descriptive e.g. Array.fold).

2. If your modules are imported qualified the suffix is redundant and
thus wastes space. Consider for example foldU imported qualified from
the module Data.Array as Array.foldU. Array.fold would have been
better in this case.

* Pros of using qualified imports

1. Your code will be more robust against additions to modules you
depend on. Consider this snippet:

import Some.Module

-- An utility function that the author of the above module forgot to include.
foo = ...

Now the author of Some.Module releases a new version of his or her
package which include `foo' and your code no longer compiles. If you
have no imports on the form

import Some.Module

your package can specify more relaxed version dependencies on other
packages [3].

2. It's easier to see from where an identifier is imported. Just check
the functions module prefix (e.g. Array in Array.foo). If you renamed
the import using import Foo as F you might have to check the import
list.

* Explicit, unqualified imports

An alternative to qualified imports that shares some of benefits is to
import names unqualified but explicitly enumerate the function names
e.g.

import Some.Module (foo, bar)

It is still not immediately obvious what module a name refers too when
using this style (point 2 under pros above) but it is at least easier
to find than when implicitly importing all names from a module. This
is my current preference for infix operators as they look quite ugly
qualified with a module name. It might also make sense if a few
functions (e.g. parser combinators) are used a lot in the same module
(e.g. an HTTP parser).

* Why is this practice common in Haskell

Here are some guesses:

1. It's common in papers. However, papers and libraries are quite
different. The former usually build up a small vocabulary and having
short names is a big win. Papers rarely make use of the module system
at all. The latter are included as pieces of large programs and their
names need to be adjusted accordingly.

2. It's the default. You have to add "qualified" to all your imports
to make them qualified. In most language imports are qualified by
default. I think the latter would have been a better choice but we
have to live with the current design so bite the bullet and add those
qualified keywords to your imports.

3. Lack of common interfaces. An example would be two different set
implementations that essentially have the same interface but since
there is no explicitly shared interface, defined using a type class,
you sometimes end up with different names. The lack of type families
might explain why e.g. collection classes don't share interfaces.

4. Haskell is a very expressive language. You can often write a whole
function definition on one line! Adding those module qualifications
makes your code slightly longer and it might just break your beautiful
one liner into two lines.

* Summary

Whenever you write a library design it so it works well with qualified
imports. That means leaving namespacing issues to the module system.
Try to think of how code using your functions will look like if it
uses them qualified.

* References

1. http://www.haskell.org/haskellwiki/Import_modules_properly
2. http://www.haskell.org/haskellwiki/Qualified_names
3. See section 2 Version numbers  -
http://www.haskell.org/haskellwiki/Library_versioning_policy

Cheers,

Johan


More information about the Haskell-Cafe mailing list