Haskell for the working programmer
Up to this point, we’ve been covering a subset of the Haskell language which would look unusual to an experienced Haskell programmer. In our quest to make side-effects, we’ve skipped over a lot of useful functionality which makes programming in Haskell more pleasant. Now that you’re able to write useful programs in Haskell, we can explore what day-to-day Haskell code looks like.
We’ll also go over some common side effects we can use in the IO monad.
Throughout this post, I’ll be including examples from a small command line application which manages the rewards program for a fictional company called Haskell’s Curry House.
Standard input and output
In order to interact with the Haskell’s Curry House Rewards Program Manager, the user will have to type commands into the command line terminal. Since reading from and writing to the command line are side-effects, we’ll need to work with a monad to perform these operations. Haskell provides the IO monad to allow us to write programs which interact with the standard input and output generated by the command line.
The following functions are used for getting standard input:
getChar :: IO Char --Get a single character getLine :: IO String --Get a line getContents :: IO String --Get all user input lazily interact :: (String -> String) -> IO () --All input is passed to the function which is the first parameter of interact readIO :: Read a => String -> IO a --Converts the String read in to a value of type a readLn :: Read a => IO a --Converts the line read in to a value of type a
If you are using getChar or getLine in a loop, you can detect the end of input using isEOF:
isEOF :: IO Bool
An end of file (EOF) signal is typically sent with a shortcut like Ctrl-D on Linux or Ctrl-Z on Windows.
The following functions are used for printing to standard output:
putChar :: Char -> IO () --Prints a character to standard output putStr :: String -> IO () --Prints a string to standard output putStrLn :: String -> IO () --Prints a string to standard output followed by a newline character print :: Show a => a -> IO () --Prints a value of type a to standard output
Notice that each of these functions takes an input which is not in the IO monad and evaluates to a value which is in the IO monad. For all output functions, they each evaluate to the null tuple () value because they don’t compute any values, they only produce side effects.
You can flush the buffered standard output with the following command. This is required to force the output to display on screen:
import System.IO --Required import for hFlush hFlush stdout --Flush standard output hFlush stderr --Flush standard error
Let’s combine these together into a function which will take a customer name, an menu item that the person purchased, and the reward points that they earned and print out a new record for that person.
Here’s a function which will generate a record containing the customer name, menu item ordered and points earned:
generateRecord :: IO (String, String, Int) generateRecord = putStr "Enter customer name:\n" >> hFlush stdout >> getLine >>= (\name -> putStr "Enter menu item:\n" >> hFlush stdout >> getLine >>= (\menuItem -> putStr "Enter number of reward points:\n" >> hFlush stdout >> readLn >>= (\rewardPoints -> return (name, menuItem, rewardPoints))))
“\n” here represents a newline character.
You should recognize the >> and >>= bind operators from the previous post on monads. In this case, >> and >>= are used to compose I/O functions together.
We use >>= bind when we want to take the result of an IO operation and use it in a later function evaluation as a parameter. For example, getLine >>= (\name -> …) takes the value of IO String produced by evaluating getLine and binds it to the name parameter in the lambda which follows. The name parameter is available to all subsequently nested lambdas, as you can see in the final line of the function, which returns a tuple with each field of the record we are generating.
We use >> bind when the function we’re composing doesn’t produce any useful value. For example, putStr doesn’t produce a value which we want to use, so we discard it’s value with the pass-through bind (>>). We could also have discarded the result of putStr using regular bind by pattern matching _ in the subsequent lambda like this: putStr >>= (\_ -> …). This is an uncommon pattern however, so you should probably use >> instead of doing this.
Now let’s look at a function which will print the record to standard output:
prettyPrintRecord :: (String, String, Int) -> IO () prettyPrintRecord (name, menuItem, rewardPoints) = putStr "Customer: " >> putStr name >> putStr ", " >> putStr menuItem >> putStr ", " >> print rewardPoints >> putStr "\n"
Since this function is just a chain of putStr commands, we can just use >> bind for each printout.
We can call these two functions together in main like this using >>= bind to take the result of generateRecord, which has type IO (String, String, Int), and pass it to prettyPrintRecord, which expects a type of (String, String, Int) as an input:
main = generateRecord >>= prettyPrintRecord
Command line arguments
You can get the command line arguments using getArgs:
import System.Environment --Required for getArgs getArgs :: IO [String] getProgName :: IO String
getArgs returns a list of strings entered into the command line after the program name. getProgName returns the name of the program.
Let’s look at an example where we print the name of the Curry House records file which is entered using a command line argument:
printRecordFileName :: [String] -> IO () printRecordFileName args = putStr "Record file: " >> putStrLn (head args) main = getArgs >>= (\args -> printRecordFileName args >> generateRecord >>= prettyPrintRecord)
head args gets the first element of the args list. Note that there is a slight problem with this code, if the user did not enter any arguments in the command line, head args will fail with an exception. We will fix this problem soon.
Basic file I/O in Haskell is done using the withFile:
import System.IO --Required for withFile withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
withFile takes a FilePath, which is just a type synonym for String, an IOMode, which controls whether a file is read from, written to, or appended to, and a function from Handle to IO r, where r is a type variable which you choose. withFile evaluates to the value of the function.
Let’s have a quick look at IOMode:
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
Using IOMode, you can specify what kind of access the program should have to the file.
withFile will open a file using the IOMode, execute the function provided as an argument on the file Handle, and close the file when it finishes executing the function.
You can read the size of a file in bytes using hFileSize and truncate or extend a file to a particular size using hSetFileSize:
hFileSize :: Handle -> IO Integer --Get the file size in bytes hSetFileSize :: Handle -> Integer -> IO () --Set the file size in bytes
You can read from a file Handle using the following functions:
hGetChar :: Handle -> IO Char --Get one character from file hGetLine :: Handle -> IO String --Get one line from file hGetContents :: Handle -> IO String --Get the entire contents of a file into a string
You can also peek at the next character in the file without advancing the handle using hLookAhead:
hLookAhead :: Handle -> IO Char
If you are reading using hGetChar or hGetLine, you can detect when to stop by querying whether the handle is at the end of the file using hIsEOF:
hIsEOF :: Handle -> IO Bool
You can write to a file using the following functions:
hPutChar :: Handle -> Char -> IO () --Put a character into a file hPutStr :: Handle -> String -> IO () --Put a string into a file hPutStrLn :: Handle -> String -> IO () --Put a string into a file followed by a newline hPrint :: Show a => Handle -> a -> IO () --Put a value of type a into a file
Let’s take a look at an example which just prints the contents of a file:
printRecordFileContents :: String -> IO () printRecordFileContents filePath = withFile filePath ReadMode (\handle -> hGetContents handle >>= (\contents -> putStrLn contents))
It is also possible to manually open and close a file using openFile and hClose:
openFile :: FilePath -> IOMode -> IO Handle --Open a file and get its handle hClose :: Handle -> IO () --Close a file handle
I do not recommend using this method because it is possible that the program may hold on to the file handle, locking the file, after it is done with it.
There is also support for seeking in a file and using binary file I/O in Haskell. Look here for more details: System.IO.
The Maybe and Either monads
In Haskell, every function must evaluate to a value, which means that failing to evaluate to a value is a kind of side-effect. As a result, all error handling and invalid value handling must be handled using monads. In order to deal with this side-effect, Haskell provides the Maybe and Either monads:
data Maybe a = Just a | Nothing deriving (Eq, Ord) data Either a b = Left a | Right b
A value of Maybe a can have a value constructor of Just a, indicating success, or Nothing, indicating failure. Either a b, can have a value constructor of Left a or Right b, with the Left a constructor representing a failed computation.
Let’s look at an example of the Either monad by fixing the problem we had earlier with head args:
printRecordFileName :: Either String String -> IO () printRecordFileName (Left err) = putStrLn err printRecordFileName (Right fileName) = putStr "Record file: " >> putStrLn fileName getFileName :: [String] -> Either String String getFileName  = Left "Failed to read file name." getFileName (fileName : args) = Right fileName main = getArgs >>= (\args -> printRecordFileName (getFileName args) >> generateRecord >>= prettyPrintRecord)
If the args list is empty, it matches the first pattern of getFileName, which evaluates to a Left value constructor with an error message. If the args list is not empty, it evaluates to a Right value constructor with the file name. Then printRecordFileName can handle the invalid input by matching against Left err and printing the error message instead of throwing an exception.
Let’s have a quick look at what this might look like if it were implemented with the Maybe monad:
printRecordFileName :: Maybe String -> IO () printRecordFileName Nothing = putStrLn "Error: File was not found." printRecordFileName (Just fileName) = putStr "Record file: " >> putStrLn fileName getFileName :: [String] -> IO (Maybe String) getFileName  = return Nothing getFileName (fileName : args) = return (Just fileName) main = getArgs >>= getFileName >>= --Evaluates to an IO (Maybe String) printRecordFileName >> --Takes a Maybe String as input, evaluates to IO () generateRecord >>= prettyPrintRecord
One interesting feature of Maybe is that you can bind a value of Maybe to a lambda or function which evaluates to a Maybe and doesn’t match a Nothing pattern. Suppose we have a function:
divide5By :: Float -> Maybe Float divide5By 0.0 = Nothing divide5By x = Just (5.0 / x) divide5By 0.0 >>= (\x -> Just (x * 10.0))
Notice how there is no error handling in the lambda? This is because the >>= bind operator for the Maybe monad just discards the function it binds to if the input to >>= is Nothing. As usual with bind, this only works for functions which evaluate to a Maybe value.
In other words, if divide5By evaluates to Nothing, the lambda never evaluates.
If any one of a >>= chain of functions in the Maybe monad evaluates to Nothing, then all subsequent operations are skipped and no invalid computations are performed.
You can do the exact same thing with the Either monad but you will get more information about the cause of the error:
divide5By :: Float -> Either String Float divide5By 0.0 = Left "Divide by Zero" divide5By x = Right (5.0 / x) divide5By 0.0 >>= (\x -> Right (x * 10.0))
The Maybe and Either monads allow us to handle invalid computations which could evaluate to Nothing or Left with very little error handling code.
Conditional evaluation with if-then-else and guards
In day to day Haskell, you’ll have two basic options for working with conditionals, guards and if-then-else statements.
You’ve already seen an example of conditionals using guards in Modeling Generalized Behaviors and Imprisoning Side Effects. Guards allow us to define several conditional statements which are checked from the top down and if the condition evaluates to True, the statement is executed as the function body. Let’s look at an example of a function which uses guards to print “Dessert ordered” if a customer ordered a dessert item or “Starter ordered” if they ordered a starter:
orderType :: String -> IO () orderType menuItem | menuItem == "Ice cream" || menuItem == "Gulab Jamun" = putStr "Dessert ordered" | menuItem == "Pakora" || menuItem == "Mini samosas" = putStr "Starter ordered" | otherwise = return ()
Otherwise catches all cases which fall through the guards above.
If-then-else statements are the second way you can create a conditional expression in Haskell. You can insert an if-then-else anywhere you would put another statement or value.
You can not specify an if-then without the else statement in Haskell because all statements must evaluate to a value. If there was no else branch, then there would not be a valid value for the if statement to evaluate to when that branch is taken, so an else branch must always be specified.
Let’s look at an example of if-then-else in a function which prints “VIP member” if the customer has more than 50 reward points:
printIsVIP :: Int -> IO () printIsVIP rewardPoints = if rewardPoints > 50 then putStr "VIP member" else return ()
Making local constants with let and where statements
Working with only recursive function calls in a function body can become very confusing, creating extremely long lines of code. In order to make your code more readable, you can define local constants using let and where statements.
We’ve seen where statements before in Modeling Generalized Behaviors and Imprisoning Side Effects. Where statements allow us to declare constants which can be referred to anywhere within a function. For example, we might want to have a function to compute a customer’s discounted price at Haskell’s Curry House:
computeCustomerDiscount :: Int -> Int -> Float computeCustomerDiscount price rewardPoints | rewardPoints > 5 = (fromIntegral price) * discountRate * tax | otherwise = (fromIntegral price) * tax where discountRate = 0.15 tax = 0.2
We can also use a let statement to define a set of local constants in a single sub-expression. This is an example of using a let expression in a function to print a raw tuple of the customer’s record to standard output:
printCustomerRecord :: (String, String, Int) -> IO () printCustomerRecord (name, menuItem, rewardPoints) = let stringRecord = show (name, menuItem, rewardPoints) in putStr stringRecord
Show is used here to convert the record into a string.
Pattern matching with case statements
Sometimes it’s useful to pattern match the type of a value inside a sub-expression. This is especially common when you’re processing Maybe values. You can use a case expression to handle this kind of pattern matching without having to write an extra function. As an example, here are a couple of functions which compute the total cost of the items a customer has purchased:
findItemCost :: String -> [(String, Float)] -> Maybe Float findItemCost _  = Nothing findItemCost itemName ((item, cost) : menuItems) | itemName == item = (let tax = 0.2 in Just (cost * tax)) | otherwise = findItemCost itemName menuItems getTotalCustomerSpent :: [(String, String, Int)] -> [(String, Float)] -> Float getTotalCustomerSpent  _ = 0.0 getTotalCustomerSpent ((name, menuItem, rewardPoints) : records) menuItems = let menuItemCost = (findItemCost menuItem menuItems) in case menuItemCost of Nothing -> getTotalCustomerSpent records menuItems Just cost -> cost + (getTotalCustomerSpent records menuItems)
Since findItemCost evaluates to a Maybe, we have to handle the case where the menuItemCost is Nothing. We could have created a separate function to perform this pattern matching, but the case expression lets us express the pattern-matching inline, making the code much more concise.
Up to this point, all of our monadic code has used the >> and >>= bind operators to compose operations which use monads together. There is a kind of syntactic sugar called “do notation” which you can put on monadic operations to make them look kind-of like imperative code.
I find do notation confusing. I think it obscures what’s going on behind the scenes with your monads. At least one person agrees with me, as can be seen here: Do notation considered harmful.
I do not recommend using do notation in your code; however, you will inevitably encounter it in your work. Let’s take a look at what the generateRecord function would look like using do notation:
generateRecord :: IO (String, String, Int) generateRecord = do putStr "Enter customer name:\n" hFlush stdout name <- getLine putStr "Enter menu item:\n" hFlush stdout menuItem <- getLine putStr "Enter number of reward points:\n" hFlush stdout rewardPoints <- readLn return (name, menuItem, rewardPoints)
This should look very familiar to you if you’re an imperative programmer. It looks like we are doing assignments with the <- operations. But these aren’t assignments at all! We’re actually using >>= bind operations to create constants in a nested monadic context.
If you saw this code before knowing how monads work, you would be completely unable to debug what happens when you get a type mismatch, or when you have to deal with an IO Maybe nested monad.
This do notation code is literally run through a “desugarer” in GHC to turn it back into the code we saw in the first example of generateRecord.
Even though I’m not fond of do notation, it is very common to see it used in Haskell, so you should at least know about it.
The example code used in this post is available in this repository: https://github.com/WhatTheFunctional/EverydayHaskell.
Continue reading Working with Lists.