REVISED: Tuesday, October 22, 2024
Haskell Modules.
I. HASKELL MODULES
Modules are a software design technique that allows you to group together related code.
A. HASKELL MAIN MODULE
A Haskell program is a collection of modules, one of which, by convention, must be called Main and must export the value main.
Module names are alphanumeric and must begin with a capital letter. For most modules, the module name must match the file name which must also begin with a capital letter. If it does not, GHCi might not be able to find it.
An I/O action will be performed when we give a module name or file name a name of Main and then run that Main module name or file name. For example:
[1 of 1] Compiling Main ( main.hs, interpreted ), modules loaded: Main.
Prelude>
B. IMPORTING HASKELL MODULES
Modules can be imported into GHCi using the keyword import as shown below:
Prelude> import System.Environment
Prelude>
Prelude> import System.IO
Prelude>
Prelude> import Test.QuickCheck
Prelude>
Prelude> :show imports
import Prelude -- always implicitly available
import System.Environment
import System.IOimport Test.QuickCheck
Prelude>
Modules can also be imported into GHCi using :module + as shown below:
Prelude> :module +Text.Printf
Prelude>
Prelude> :show imports
import Prelude -- implicit
import System.Environment
import System.IOimport Text.Printf
import Test.QuickCheck
Prelude>
After a module has been imported you can use :browse to browse all of its function type signatures. For example:
Prelude> :browse Text.Printf
data FieldFormat
= FieldFormat {fmtWidth :: Maybe Int,
fmtPrecision :: Maybe Int,
fmtAdjust :: Maybe FormatAdjustment,
fmtSign :: Maybe FormatSign,
fmtAlternate :: Bool,
fmtModifiers :: String,
fmtChar :: Char}
type FieldFormatter = FieldFormat -> ShowS
data FormatAdjustment = LeftAdjust | ZeroPad
data FormatParse
= FormatParse {fpModifiers :: String,
fpChar :: Char,
fpRest :: String}
data FormatSign = SignPlus | SignSpace
class HPrintfType t where
Text.Printf.hspr :: Handle -> String -> [Text.Printf.UPrintf] -> t
class IsChar c where
toChar :: c -> Char
fromChar :: Char -> c
type ModifierParser = String -> FormatParse
class PrintfArg a where
formatArg :: a -> FieldFormatter
parseFormat :: a -> ModifierParser
class PrintfType t where
Text.Printf.spr :: String -> [Text.Printf.UPrintf] -> t
errorBadArgument :: a
errorBadFormat :: Char -> a
errorMissingArgument :: a
errorShortFormat :: a
formatChar :: Char -> FieldFormatter
formatInt :: (Integral a, Bounded a) => a -> FieldFormatter
formatInteger :: Integer -> FieldFormatter
formatRealFloat :: RealFloat a => a -> FieldFormatter
formatString :: IsChar a => [a] -> FieldFormatter
hPrintf :: HPrintfType r => Handle -> String -> r
perror :: String -> a
printf :: PrintfType r => String -> r
vFmt :: Char -> FieldFormat -> FieldFormat
Prelude>
C. UNLOADING HASKELL MODULES
Modules can also be unload from GHCi by using :module - as shown below:
Prelude> :module -System.IO
Prelude>
Prelude> :show imports
import Prelude -- implicit
import System.Environment
import Test.QuickCheck
import Text.Printf
Prelude>
II. MODULE SKELETON
-- MODULE DECLARATION
module Main where
-- Main is the name of module declaration, always capitalize the module name's first letter.
-- IMPORTS
import Data.List ()
import System.IO ()
-- MAIN PROGRAM
main :: IO ()
main = putStrLn "Hello, world!" -- Notice main starts with lowercase m
-- FUNCTIONS
Any import directives must appear in a group at the beginning of a module. They must appear after the module declaration shown above, but before all other code. We cannot, for example, scatter them throughout a source file.
III. WRITING YOUR FIRST HASKELL MODULE
If we want to do some serious programming, it is very likely that our program will increase in size. An overview of a lengthy file is a difficult task. Therefore, long projects are divided into parts, containing pieces of a program that belong together. These parts are called modules.
A Haskell module can be typed into a text file that you can edit. You can use any text editor with an understanding of code and indentation; e.g., Notepad++, to type a Haskell module or as previously mentioned Visual Studio Code.
First, "Copy Paste" the following module example into your text editor and "File Save As" Factorial.hs into your working directory.
First, "Copy Paste" the following module example into your text editor and "File Save As" Factorial.hs into your working directory.
-- My first Haskell module!
module Factorial where
factorial :: (Eq a, Num a) => a -> a
factorial 0 = 1 -- Base case.
factorial n = n * factorial (n-1) -- Recursive case.
The factorial type signature shown above defines a generic type signature which creates a function that works for several types. The compiler will infer the actual types using type inference.
Second, load Factorial.hs into GHCi as follows:
Prelude> :load Factorial
[1 of 1] Compiling Factorial ( Factorial.hs, interpreted )
Ok, modules loaded: Factorial.
Prelude>
Third, call the factorial function as follows:
Prelude> factorial 3 -- Notice that the f in factorial is lowercase!
Prelude>
Fourth, let's look at what happens when you execute factorial 3:
3 isn't 0, so we calculate the factorial of 2
2 isn't 0, so we calculate the factorial of 1
1 isn't 0, so we calculate the factorial of 0
0 is 0, so we return 1.
To complete the calculation for factorial 3, we multiply the current number, 1, by the factorial of 0, which is 1, obtaining 1 = (1 × 1).
To complete the calculation for factorial 3, we multiply the current number, 2, by the factorial of 1, which is 1, obtaining 2 = (2 × 1 × 1).
To complete the calculation for factorial 3, we multiply the current number, 3, by the factorial of 2, which is 2, obtaining 6 = (3 × 2 × 1 × 1).
Factorial takes a single non-negative integer as an argument, finds all the positive integers less than or equal to "n", and multiplies them all together.
3*2*1*1 = 6 -- The last 1 is the factorial of zero.
A. SAVING TO ANY DIRECTORY OR FOLDER
You can save Factorial.hs anywhere you like, but if you save it somewhere other than your working directory you will need to change to that directory in GHCi:
Prelude> :cd dir
Prelude>
Where dir is the directory (or folder) in which you saved Factorial.hs.
To load a Haskell module into GHCi, use the import command:
Prelude> import Factorial
Each module must import all modules containing functions used within its definitions. Usually, some functions will be shared among all modules. In this case, each module must import the module where the function is defined. Modularization is beneficial in functional programming for several reasons. Program units get smaller and easier to manage. Control over connections among various modules is transparent for future use and error detecting. A number of files can be loaded in a convenient way by loading the project file only.
Bird, R. (2015). Thinking Functionally with Haskell. Cambridge, England: Cambridge University Press.
Davie, A. (1992). Introduction to Functional Programming Systems Using Haskell. Cambridge, England: Cambridge University Press.
Goerzen, J. & O'Sullivan, B. & Stewart, D. (2008). Real World Haskell. Sebastopol, CA: O'Reilly Media, Inc.
Hutton, G. (2007). Programming in Haskell. New York: Cambridge University Press.
Lipovača, M. (2011). Learn You a Haskell for Great Good!: A Beginner's Guide. San Francisco, CA: No Starch Press, Inc.
Thompson, S. (2011). The Craft of Functional Programming. Edinburgh Gate, Harlow, England: Pearson Education Limited.
factorial 0 = 1 -- Base case.
factorial n = n * factorial (n-1) -- Recursive case.
The factorial type signature shown above defines a generic type signature which creates a function that works for several types. The compiler will infer the actual types using type inference.
Second, load Factorial.hs into GHCi as follows:
Prelude> :load Factorial
[1 of 1] Compiling Factorial ( Factorial.hs, interpreted )
Ok, modules loaded: Factorial.
Prelude>
Third, call the factorial function as follows:
Prelude> factorial 3 -- Notice that the f in factorial is lowercase!
6
it :: (Num a, Eq a) => aFourth, let's look at what happens when you execute factorial 3:
3 isn't 0, so we calculate the factorial of 2
2 isn't 0, so we calculate the factorial of 1
1 isn't 0, so we calculate the factorial of 0
0 is 0, so we return 1.
To complete the calculation for factorial 3, we multiply the current number, 1, by the factorial of 0, which is 1, obtaining 1 = (1 × 1).
To complete the calculation for factorial 3, we multiply the current number, 2, by the factorial of 1, which is 1, obtaining 2 = (2 × 1 × 1).
To complete the calculation for factorial 3, we multiply the current number, 3, by the factorial of 2, which is 2, obtaining 6 = (3 × 2 × 1 × 1).
Recursion is analogous to the concept of induction in mathematics.
We can see how the result of the recursive call is calculated first, then combined using multiplication.
Factorial takes a single non-negative integer as an argument, finds all the positive integers less than or equal to "n", and multiplies them all together.
The factorial of any number is just that number multiplied by the factorial of the number one less than it. There is one exception to this. If we ask for the factorial of 0, we do not want to multiply 0 by the factorial of -1. In fact, we just say the factorial of 0 is 1. We define it to be so. So, 0 is the base case for the recursion. When we get to 0 we can immediately say that the answer is 1, without using recursion.
3*2*1*1 = 6 -- The last 1 is the factorial of zero.
For modularization of our own definitions; put exactly one module in a single file. Give the same names to files and modules. Thus the example above is saved as Factorial.hs.
Since the module name must begin with a capital letter, the file name must also start with a capital letter.
Since the module name must begin with a capital letter, the file name must also start with a capital letter.
A. SAVING TO ANY DIRECTORY OR FOLDER
You can save Factorial.hs anywhere you like, but if you save it somewhere other than your working directory you will need to change to that directory in GHCi:
Prelude> :cd dir
Where dir is the directory (or folder) in which you saved Factorial.hs.
To load a Haskell module into GHCi, use the import command:
Prelude> import Factorial
Prelude>
Congratulations, you have just written your first Haskell module!
IV. COMBINING HASKELL FILES INTO PROJECTS
Each module must import all modules containing functions used within its definitions. Usually, some functions will be shared among all modules. In this case, each module must import the module where the function is defined. Modularization is beneficial in functional programming for several reasons. Program units get smaller and easier to manage. Control over connections among various modules is transparent for future use and error detecting. A number of files can be loaded in a convenient way by loading the project file only.
V. CONCLUSION
In this tutorial, you have been introduced to the importance of using Haskell modules when you code your programs.
VI. REFERENCES
Bird, R. (2015). Thinking Functionally with Haskell. Cambridge, England: Cambridge University Press.
Davie, A. (1992). Introduction to Functional Programming Systems Using Haskell. Cambridge, England: Cambridge University Press.
Goerzen, J. & O'Sullivan, B. & Stewart, D. (2008). Real World Haskell. Sebastopol, CA: O'Reilly Media, Inc.
Hutton, G. (2007). Programming in Haskell. New York: Cambridge University Press.
Lipovača, M. (2011). Learn You a Haskell for Great Good!: A Beginner's Guide. San Francisco, CA: No Starch Press, Inc.
Thompson, S. (2011). The Craft of Functional Programming. Edinburgh Gate, Harlow, England: Pearson Education Limited.