Surprises of the Haskell module system (part 1)
December 25, 2012
While basics of the Haskell module system are easy to understand, there are certainly interesting corner cases and non-obvious (mis)features. In this and the following articles we will explore some curious examples.
All of these are documented in the Haskell report, but the tricky details are hard to notice and recognize (although I encourage you to read the report nevertheless!). 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 foo = 42
This one is easy — all the locally defined types, classes and values are exported.
We might presume that something similar happens when we omit the module header entirely — like if we just put
foo = 42
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 some Haskell snippet compile. You may put it into a file and try
ghc test.hs. But even if the snippet 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, above is 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 export 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.