Yet Another Haskell Tutorial/Modules

Haskell
Yet Another Haskell Tutorial
Preamble
Introduction
Getting Started
Language Basics (Solutions)
Type Basics (Solutions)
IO (Solutions)
Modules (Solutions)
Advanced Language (Solutions)
Advanced Types (Solutions)
Monads (Solutions)
Advanced IO
Recursion
Complexity

In Haskell, program subcomponents are divided into modules. Each module sits in its own file and the name of the module should match the name of the file (without the ".hs" extension, of course), if you wish to ever use that module in a larger program.

For instance, suppose I am writing a game of poker. I may wish to have a separate module called "Cards" to handle the generation of cards, the shuffling and the dealing functions, and then use this "Cards" module in my "Poker" modules. That way, if I ever go back and want to write a blackjack program, I don't have to rewrite all the code for the cards; I can simply import the old "Cards" module.


Exports

edit

Suppose as suggested we are writing a cards module. I have left out the implementation details, but suppose the skeleton of our module looks something like this:

module Cards
    where

data Card = ...
data Deck = ...

newDeck :: ... -> Deck
newDeck = ...

shuffle :: ... -> Deck -> Deck
shuffle = ...

-- 'deal deck n' deals 'n' cards from 'deck'
deal :: Deck -> Int -> [Card]
deal deck n = dealHelper deck n []

dealHelper = ...

In this code, the function deal calls a helper function dealHelper. The implementation of this helper function is very dependent on the exact data structures you used for Card and Deck so we don't want other people to be able to call this function. In order to do this, we create an export list, which we insert just after the module name declaration:

module Cards ( Card(),
               Deck(),
               newDeck,
               shuffle,
               deal
             )
    where

...

Here, we have specified exactly what functions the module exports, so people who use this module won't be able to access our dealHelper function. The () after Card and Deck specify that we are exporting the type but none of the constructors. For instance if our definition of Card were:

data Card = Card Suit Face
data Suit = Hearts
          | Spades
          | Diamonds
          | Clubs
data Face = Jack
          | Queen
          | King
          | Ace
          | Number Int

Then users of our module would be able to use things of type Card, but wouldn't be able to construct their own Cards and wouldn't be able to extract any of the suit/face information stored in them.

If we wanted users of our module to be able to access all of this information, we would have to specify it in the export list:

module Cards ( Card(Card),
               Suit(Hearts,Spades,Diamonds,Clubs),
               Face(Jack,Queen,King,Ace,Number),
               ...
             )
    where

...

This can get frustrating if you're exporting datatypes with many constructors, so if you want to export them all, you can simply write (..), as in:

module Cards ( Card(..),
               Suit(..),
               Face(..),
               ...
             )
    where

...

And this will automatically export all the constructors.



Imports

edit

There are a few idiosyncrasies in the module import system, but as long as you stay away from the corner cases, you should be fine. Suppose, as before, you wrote a module called "Cards" which you saved in the file "Cards.hs". You are now writing your poker module and you want to import all the definitions from the "Cards" module. To do this, all you need to do is write:


module Poker
    where

import Cards

This will enable you to use any of the functions, types and constructors exported by the module "Cards". You may refer to them simply by their name in the "Cards" module (as, for instance, newDeck), or you may refer to them explicitly as imported from "Cards" (as, for instance, Cards.newDeck). It may be the case that two modules export functions or types of the same name. In these cases, you can import one of the modules qualified which means that you would no longer be able to simply use the newDeck format but must use the longer Cards.newDeck format, to remove ambiguity. If you wanted to import "Cards" in this qualified form, you would write:


import qualified Cards

Another way to avoid problems with overlapping function definitions is to import only certain functions from modules. Suppose we knew the only function from "Cards" that we wanted was newDeck, we could import only this function by writing:

import Cards (newDeck)

On the other hand, suppose we knew that that the deal function overlapped with another module, but that we didn't need the "Cards" version of that function. We could hide the definition of deal and import everything else by writing:


import Cards hiding (deal)

Finally, suppose we want to import "Cards" as a qualified module, but don't want to have to type Cards. out all the time and would rather just type, for instance, C. -- we could do this using the as keyword:


import qualified Cards as C

These options can be mixed and matched -- you can give explicit import lists on qualified/as imports, for instance.



Hierarchical Imports

edit

Though technically not part of the Haskell 98 standard, most Haskell compilers support hierarchical imports. This was designed to get rid of clutter in the directories in which modules are stored. Hierarchical imports allow you to specify (to a certain degree) where in the directory structure a module exists. For instance, if you have a "haskell" directory on your computer and this directory is in your compiler's path (see your compiler notes for how to set this; in GHC it's "-i", in Hugs it's "-P"), then you can specify module locations in subdirectories to that directory.

Suppose instead of saving the "Cards" module in your general haskell directory, you created a directory specifically for it called "Cards". The full path of the Cards.hs file is then haskell/Cards/Cards.hs (or, for Windows haskell\Cards\Cards.hs). If you then change the name of the Cards module to "Cards.Cards", as in:

module Cards.Cards(...)
    where

...

You could then import it in any module, regardless of this module's directory, as:

import Cards.Cards

If you start importing these module qualified, I highly recommend using the as keyword to shorten the names, so you can write:

import qualified Cards.Cards as Cards

... Cards.newDeck ...

instead of:

import qualified Cards.Cards

... Cards.Cards.newDeck ...

which tends to get ugly.



Literate Versus Non-Literate

edit

The idea of literate programming is a relatively simple one, but took quite a while to become popularized. When we think about programming, we think about the code being the default mode of entry and comments being secondary. That is, we write code without any special annotation, but comments are annotated with either -- or {- ... -}. Literate programming swaps these preconceptions.

There are two types of literate programs in Haskell; the first uses so-called Bird-scripts and the second uses LaTeX-style markup. Each will be discussed individually. No matter which you use, literate scripts must have the extension lhs instead of hs to tell the compiler that the program is written in a literate style.

Bird-scripts

edit

In a Bird-style literate program, comments are default and code is introduced with a leading greater-than sign (">"). Everything else remains the same. For example, our Hello World program would be written in Bird-style as:

This is a simple (literate!) Hello World program.

> module Main
>     where

All our main function does is print a string:

> main = putStrLn "Hello World"

Note that the spaces between the lines of code and the "comments" are necessary (your compiler will probably complain if you are missing them). When compiled or loaded in an interpreter, this program will have exactly the same properties as the non-literate version from the section on Files.

LaTeX-scripts

edit

LaTeX is a text-markup language very popular in the academic community for publishing. If you are unfamiliar with LaTeX, you may not find this section terribly useful.

Again, a literate Hello World program written in LaTeX-style would look like:

This is another simple (literate!) Hello World program.

\begin{code}
module Main
    where
\end{code}

All our main function does is print a string:

\begin{code}
main = putStrLn "Hello World"
\end{code}

In LaTeX-style scripts, the blank lines are not necessary.