REVISED: Monday, October 21, 2024
Haskell Beginner Program Skeleton: A "Read File Write File" Tutorial.
I. READ FILE WRITE FILE EXAMPLE
Use your editor to save the following input.txt file:
A bug sat in a silver flower
thinking silver thoughts.
A bigger bug out for a walk
climbed up that silver flower stalk
and snapped the small bug down his jaws
without a pause
without a care
for all the bug's small silver thoughts.
It isn't right,
it isn't fair,
that that big bug ate that little bug
because that little bug was there.
He also ate his underwear.
Also use your editor to save output.txt file as an empty file.
A. SKELETON
module Main where
import Data.Char
main = do
inh <- openFile "input.txt" ReadMode
outh <- openFile "output.txt" WriteMode
-- Your code goes here, replacing the function mainloop call.
hClose inh
hClose outh
-- And, your code goes here, replacing the function mainloop signature and definition.
B. FLESH ON BONES
1. SAVE
Use your editor to save the following Main.hs file:
Use your editor to save the following Main.hs file:
module Main where
import System.IO
import Data.Char(toUpper)
main :: IO () -- The main function signature.
main = do -- Calls the main function.
inh <- openFile "input.txt" ReadMode
outh <- openFile "output.txt" WriteMode
mainloop inh outh -- Calls the mainloop function.
hClose inh
hClose outh
mainloop :: Handle -> Handle -> IO () -- The mainloop function signature.
mainloop inh outh = -- The mainloop function definition.
do ineof <- hIsEOF inh
if ineof
then return ()
else do inpStr <- hGetLine inh
hPutStrLn outh (map toUpper inpStr)
mainloop inh outh
The above program is color coded: Variables are blue. Haskell keywords are red. User defined functions are orange.
2. LOAD
As shown below, load the above Main.hs file into GHCi:
Prelude> :load Main.hs
[1 of 1] Compiling Main ( Main.hs, interpreted )
Ok, modules loaded: Main.
Prelude>3. RUN
As shown below, run the module Main, function main, in GHCi:
Prelude> main
Prelude>
When you run main in GHCi it looks like nothing has happened; however, lots of things have happened. You have read from the input.txt file, and written to the output.txt file. At first it looks odd, because the program did not write output to your screen.
Open the output.txt file with your editor and you will see the following:
A BUG SAT IN A SILVER FLOWER
THINKING SILVER THOUGHTS.
A BIGGER BUG OUT FOR A WALK
CLIMBED UP THAT SILVER FLOWER STALK
AND SNAPPED THE SMALL BUG DOWN HIS JAWS
WITHOUT A PAUSE
WITHOUT A CARE
FOR ALL THE BUG'S SMALL SILVER THOUGHTS.
IT ISN'T RIGHT,
IT ISN'T FAIR,
THAT THAT BIG BUG ATE THAT LITTLE BUG
BECAUSE THAT LITTLE BUG WAS THERE.
HE ALSO ATE HIS UNDERWEAR.
THINKING SILVER THOUGHTS.
A BIGGER BUG OUT FOR A WALK
CLIMBED UP THAT SILVER FLOWER STALK
AND SNAPPED THE SMALL BUG DOWN HIS JAWS
WITHOUT A PAUSE
WITHOUT A CARE
FOR ALL THE BUG'S SMALL SILVER THOUGHTS.
IT ISN'T RIGHT,
IT ISN'T FAIR,
THAT THAT BIG BUG ATE THAT LITTLE BUG
BECAUSE THAT LITTLE BUG WAS THERE.
HE ALSO ATE HIS UNDERWEAR.
4. COMPILE
As shown below, compile Main.hs in GHC:
Prelude> :! ghc --make "*Main"
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main.exe ...
Prelude>
The compile is shown so you know it compiles without error.
C. COMMENTS
Use the program shown above as an example. Rewrite the example and make it your own program.
Repeat 1. thru 3. above until your new program works the way you want it to work. Each time you make changes in your editor to your program, make sure you save the file and load it in GHCi.
The Haskell keywords used in this tutorial are discussed below:
1. openFile
Prelude> :type openFile
openFile :: FilePath -> IOMode -> IO Handle
Prelude>
inh <- openFile "input.txt" ReadMode
Opens the input.txt file in ReadMode and creates the input file handle inh. From this point in the program the input.txt file will be referred to by the name of its handle inh. For the time being, you can think of inh as a nickname for input.txt.
outh <- openFile "output.txt" WriteMode
Opens the output.txt file in WriteMode and creates the output file handle outh. From this point in the program the output.txt file will be referred to by the name of its handle nickname outh.
2. ReadMode
ReadMode :: IOMode
Prelude>
inh <- openFile "input.txt" ReadMode
inh <- openFile "input.txt" ReadMode
When openFile opens the input.txt file, ReadMode flags the file for read mode.
WriteMode :: IOMode
Prelude>
outh <- openFile "output.txt" WriteMode
outh <- openFile "output.txt" WriteMode
When openFile opens the output.txt file, WriteMode flags the file for write mode.
hClose :: Handle -> IO ()
Prelude>
hClose inh
hClose outh
hClose closes the input.txt file handle nicknamed inh; which is the same as closing the input.txt file.
hClose closes the output.txt file handle nicknamed outh; which is the same as closing the output.txt file.
5. hIsEOF
Prelude> :type hIsEOF
hIsEOF :: Handle -> IO Bool
Prelude>
do ineof <- hIsEOF inh
do ineof <- hIsEOF inh
hIsEOF inh returns True if no further input can be taken from inh; otherwise, it returns False. Then either True or False is bound to ineof by the <- function arrow. hIsEOF inh means execute hIsEOF inh in the IO monad and bind the result in ineof.
Prelude> :type return
return :: Monad m => a -> m a
Prelude>
In general, for any value, there is a computation which "does nothing", and produces that result. This is given by defining the function return for the given monad.
mainloop :: Handle -> Handle -> IO () -- The mainloop function signature.
mainloop inh outh = -- The mainloop function definition.
do ineof <- hIsEOF inh
if ineof
then return ()
else do inpStr <- hGetLine inh
hPutStrLn outh (map toUpper inpStr)
mainloop inh outh
In our example the function mainloop has two arguments: inh and outh. The first do statement binds ineof with a True or False from hIsEof inh.
if in Haskell must always have a then and an else.
If ineof is True we are at the end of the input.txt file, then return () is executed and we return to the main function, close the input stream handle, close the output stream handle, and exit the main function.
If ineof is False the else second do first statement is executed binding inStr with the next line of characters from inh. The function map takes the function toUpper which takes a list of lower case characters inpSTR and produces a list of upper case characters which hPutStrLn writes to the output.txt file handle outh output stream. The second do third statement recursively calls the function mainloop with the two arguments inh and outh.
7. hGetLine
Prelude> :type hGetLine
hGetLine :: Handle -> IO String
Prelude>
else do inpStr <- hGetLine inh
else do inpStr <- hGetLine inh
hGetLine reads a line from the input.txt file handle inh input stream and <- binds that line to inpStr.
8. hPutStrLn
hPutStrLn :: Handle -> String -> IO ()
Prelude>
hPutStrLn outh (map toUpper inpStr)
The function hPutStrLn writes a string to the output.txt file handle outh output stream and adds a newline character.
9. map
Prelude> :type map
map :: (a -> b) -> [a] -> [b]
Prelude>
hPutStrLn outh (map toUpper inpStr)
In general, the function map takes any function of type (a -> b) and yields a function that takes a list of a's [a] and produces a list of b's [b]. In our example, the function map takes the function toUpper which takes a list of lower case characters inpSTR and produces a list of upper case characters which hPutStrLn writes to the output.txt file handle outh output stream.
10. toUpper
Prelude> :type toUpper
toUpper :: Char -> Char
Prelude>
hPutStrLn outh (map toUpper inpStr)
The function toUpper converts a letter to the corresponding upper-case letter, leaving any other character unchanged. Any Unicode letter which has an upper-case equivalent is transformed.
11. ()
Prelude> :type ()
() :: ()
Prelude>
then return ()
In general, the () is an empty tuple, pronounced “unit”, indicating that there is no return value. In our example, then return () is executed and we return to the main function, close the input stream handle, close the output stream handle, and exit the main function.
II. CONCLUSION
In this tutorial, you have received an introduction to the Haskell beginner program skeleton "read file write file".
III. 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.