Surprises of the Haskell module system (part 1)
While the basics of the Haskell module system are easy to understand, there are certainly interesting corner cases and non-obvious (mis)features.
All of these are documented in the Haskell report, but the tricky details may be hard to notice. Another good read on this subject is A Formal Specification for the Haskell 98 Module System by Iavor S. Diatchki, Mark P. Jones, and Thomas Hallgren.
Empty module header
What happens when the export list is omitted, like in the following example?
module M where = 42foo
This one is easy: all the locally defined types, classes and values are exported.
We might assume that something similar happens when we omit the module header altogether, like if we put
in a file by itself.
Contrary to this intuition, and according to the standard, when you omit the module header, it is assumed to be
module Main(main) where.
Sometimes you want to check whether a piece of Haskell code compiles. You may put it into a file and try
ghc test.hs. But even if the code itself doesn’t contain errors, you’ll get a message
test.hs:1:1: The function `main' is not defined in module `Main'
The message may seem confusing at first: what’s the module
Main it’s talking about, and why do I need this
main function? Well, now you know the answer.
You probably know that you can re-export a whole module from your own module. The syntax is
module M (module N) where ...
The semantics of this re-export is a bit tricky, though. For example, what can we say about the following module?
module M (module Data.List) where import qualified Data.List
You’d probably think that module
M now exports everything that is exported by
Data.List. Not at all! The standard says (emphasis is mine):
module Mnames the set of all entities that are in scope with both an unqualified name
eand a qualified name
Since we imported
Data.List qualified, nothing from it is in scope under an unqualified name. So,
M doesn’t export anything. Right? Not quite.
The last thing we need to observe here is that although the single module imported by
M explicitly is
Data.List, there’s always an implicit import —
Prelude. And these two share quite a lot in common — functions like
length etc. All these satisfy the criterion above — they are in scope with both unqualified and qualified names, although these names come from different modules.
To summarize, the module
M defined above will export exactly the intersection of
Data.List, plus instances exported by either of these modules.