No export lists

From HaskellWiki
Jump to navigation Jump to search

Here I argue to remove export lists in Haskell in the long term and replace them by locally defined export modes. In the middle term both export declaration types could coexist.

Status quo

In Haskell 98 modules, all declared identifiers are exported by default.

module Important where

-- automatically exported
foo :: Int
foo = 2

That is, other modules can import and use identifiers like foo. You have finer control by export lists. They enable you to keep implementation details private and prevent users of the module from relying on interna that might change in future.

module Important (foo, Number(One,Two,Three)) where

-- manually exported
foo :: Int
foo = 2

data Number = -- type is exported
     Zero  -- heretical things kept private
   | One   -- public
   | Two   -- public
   | Three -- public
   | Many  -- private

You can compare the export list with the definition modules of Modula-2 or the interface modules of Modula-3, and the module body with the implementation modules of the Modula dialects.

Even more, the documentation extraction tool Haddock allows to divide a module into sections, and it allows this to do it either in the module body

module Important (foo, Number(One,Two,Three)) where

{- * Important functions -}

{- | most important function -}
foo :: Int
foo = 2

{- * Important data types -}

{- | most important data type -}
data Number =
     Zero
   | One
   | Two
   | Three
   | Many

or in the export list.

module Important (
   {- * Important functions -}
   foo,

   {- * Important data types -}
   Number(One,Two,Three)) where


{- | most important function -}
foo :: Int
foo = 2

{- | most important data type -}
data Number =
     Zero
   | One
   | Two
   | Three
   | Many

Problems

The problem is that the export list duplicates data and the redundant information must be maintained. Whenever you add, remove, change the name of a function, you have to apply this change on the export list. If you comment out a malicious part of a module to get the rest compiled, you have to comment out parts of the export list, too. Haddock gives you the choice whether to define sections in the export list or the module body. However, what is the right choice?


Solutions

The language Oberon, the successor of Modula-2 by Niklaus Wirth, goes a different way. Like Haskell, Oberon has only one file per module. The interface file for a module is extracted by the Oberon compiler from the module file. Whether an identifier is exported is defined right at the declaration of that identifier by an asterisk. Adapted to Haskell this would look like

module Important where

{- * Important functions -}

{- | most important function -}
foo* :: Int
foo = 2

{- * Important data types -}

{- | most important data type -}
data Number* =
     Zero
   | One*
   | Two*
   | Three*
   | Many

Whenever you define a new identifier, you can decide on exporting just where the cursor is. The reader sees whether an identifier is exported just where it is introduced. Every change including deletion of a declaration automatically alters the exported data. Cute, isn't it?

Identifiers that are only re-exported (that is, imported from another module and exported as they are), may get a star attached in the import list, since this is the way, how the come into the module. Currently it is possible to import a bunch of identifiers anonymously by

import A
import B

and export them explicitly

module Important (foo, bar) where ...

This way it is not clear, where foo and bar are from. With the new syntax this is no longer the case.

module Important where

import A(foo*)
import B(bar*)