Real World Haskell Chapter 4 Solutions

Okay, in this chapter, we will apparently learn more about common techniques in FP, can’t wait!

Exercises page 84

Let’s rewrite safe versions of partial list functions

The first two functions are very straightforward: a bit of pattern matching, some identification of edge cases and we’re done.

safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:xs) = Just x

safeTail :: [a] -> Maybe [a]
safeTail [] = Nothing
safeTail (x:xs) = Just xs

safeLast :: [a] -> Maybe a
safeLast [] = Nothing
safeLast [x] = Just x
safeLast (x:xs) = safeLast xs

It gets a bit more tricky for the last one. Indeed, we need to recursively construct a list from the end to the beginning and wrap everything in a Maybe. In order to do that, I first calculate the list in a sub-function defined in a where section and then encapsulate everything in the Maybe.

 safeInit :: [a] -> Maybe [a]
 safeInit [] = Nothing
 safeInit x = Just (end x)
   where end :: [a] -> [a]
         end [a] = []
         end (x:xs) = x:(end xs)
 

SplitWith: an inverted word

This definition is so confusing… I am not sure about getting the specification right, but from what I understand:

  • We have a list we want to split in sublists.
  • We need to evaluate an expression for each list member.
  • If this evaluation is false, we need to split the main list at this point.
splitWith :: (a -> Bool) -> [a] -> [[a]]
splitWith _ [] = []
splitWith function list = [(takeWhile function list)] ++ (splitWith function remaining)
    where
        endOfList = dropWhile function list
        remaining
            | null endOfList = []
            | otherwise = tail (endOfList)

First, let’s think about edge cases. Obviously, the split of an empty list will results to an empty list.

Then, we need to write a recursive pass on the list. During this pass, we will first use the takeWhile function in order to take elements that verify the function. Then we will append these elements to the rest of the list minus the “delimiter”. I insist, you need to remove this delimiter, otherwise you’ll end up with an infinite loop.

True Story Bro'

In order to calculate the rest of the list (list minus delimiter), I used the function tail. As we seen in the last exercise, this function is quite unsafe. We need to be extra careful that we are not tailing an empty list.

Again, I am not sure that I got the specification right in the first place.

Printing first word for each input line

This exercise is quite simple, we want to:

  1. Read a file.
  2. Take the first character of each line.
  3. Print it to another file.
-- We can reuse this code which is given in
-- the previous chapter
interactWith function inputFile outputfile = do
    input <- readFile inputFile
    writeFile outputfile (function input)

main = mainWith myFunction
	where myFunction = getFirstWordsOfFile
    	      mainWith function = do
    	      args <- getArgs
    	      case args of
        	      [input,output] -> interactWith function input output
	       	      _ -> putStrLn "error: exactly two arguments needed"

firstWord :: String -> String
firstWord line = head (words line)

firstWordsOfFile :: [String] -> String
firstWordsOfFile [] = ""
firstWordsOfFile (x:xs) = (firstWord x) ++ " " ++ (firstWordsOfFile xs)

getFirstWordsOfFile :: String -> String
getFirstWordsOfFile fileStr = firstWordsOfFile (lines fileStr)

As you see, this is quite simple, there are 3 functions:

  • firstWord: which chops a line and returns the first word.
  • firstWordsOfFile: which takes the lines of the file one by one and returns only the first words.
  • getFirstWordsOfFile: which takes the file string, chop it line by line and returns the first word.

I could have used where or let in order to encapsulate the intermediates functions. But I haven’t…

Transposing text from a file

Aaaaaaand, dunno. Seriously, I do not get how to do that without the map function… Shout me out if you found the solution!

Exercises page 98

Rewriting asInt using folds

This one is easy, we just fold from the left the char expression and we keep the integer representation up to date by multiplying by 10.

import Data.Char (digitToInt)
asInt_fold :: String -> Int
asInt_fold str = foldl foldFunc 0 str
    where foldFunc :: Int -> Char -> Int
          foldFunc acc elem = (digitToInt elem) + (acc * 10)

Handling negative numbers

Okay, now we want to handle negative numbers.

asInt_fold :: String -> Int
asInt_fold (x:xs)
    | x == '-' = (-1) * asInt_fold xs
asInt_fold str = foldl foldFunc 0 str
    where foldFunc :: Int -> Char -> Int
          foldFunc acc elem = (digitToInt elem) + (acc * 10)
In order to do that, we pattern match the head of the string and we filter the - using guards.

Handling common errors

Let’s add some conditions in order to handle common errors.

asInt_fold :: String -> Int
asInt_fold [] = error "Cannot convert [] to Int"
asInt_fold (x:xs)
    | x == '-' && null xs  = error "Cannot convert this expression to Int."
    | x == '-' = (-1) * asInt_fold xs
asInt_fold str = foldl foldFunc 0 str
    where foldFunc :: Int -> Char -> Int
          foldFunc acc elem = (digitToInt elem) + (acc * 10)

Please note that I did not found any direct way to correctly handle Int overflow other than using the safeint type (which I did not implemented here).

Safe error handling using either

Here, we’re going to wrap up our error handling in the either

type ErrorMessage = String

asInt_either :: String -> Either ErrorMessage Int
asInt_either [] = Left "Cannot convert [] to Int"
asInt_either (x:xs)
    | x == '-' && null xs  = Left "Cannot convert this expression to Int."
    | x == '-' = fmap (*(-1)) (asInt_either xs)
asInt_either str = Right (foldl foldFunc 0 str)
    where foldFunc :: Int -> Char -> Int
          foldFunc acc elem = (digitToInt elem) + (acc * 10)
I did not found a way to solve this problem without using fmap which has not been introduced yet. Maybe I missed something here or maybe this is an error in the book…

Implementing concat using foldr

Nothing to say here, this is straightforward

concat_foldr :: [[a]] -> [a]
concat_foldr input = foldr foldFunc [] input
    where foldFunc :: [a] -> [a] -> [a]
          foldFunc acc elem = acc ++ elem

Implementing takewhile using foldr

Again, nothing special here.

takeWhile' :: (a -> Bool) -> [a] -> [a]
takeWhile' _ [] = []
takeWhile' pred (x:xs)
    | pred x = x:takeWhile' pred xs
    | otherwise = []

takeWhileFold :: (a -> Bool) -> [a] -> [a]
takeWhileFold pred input = foldr foldFunc [] input
    where foldFunc elem acc
            | pred elem = elem:acc
            | otherwise = []

Implementing List’s groupBy

After playing a bit with ghci, it turns out that group by is taking a 2 parameters function and a list and groups the list’s elements using the function given in parameters and the first element. As soon as it is impossible to group with the first element, a new group is created and so on. For instance:

*Main Data.List> groupBy (\x y-> x + 1 == y) [1,2,2,3]
[[1,2,2],[3]]

Okay, let’s implement that.

groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy' pred input = foldl foldFunc [] input
    where foldFunc [] elem = [[elem]]
          foldFunc acc elem
            | pred (head(last acc)) elem = (init acc) ++ [(last acc ++ [elem])]
            | otherwise = acc ++ [[elem]]
This problem turned out to be very tricky. For each element, we want to check if we want to pair it with the current subgroup.

In order to do that, we need to:

  1. Get the current subgroup using last acc.
  2. Get the first element of this subgroup which is the group “master” using head(subgroup)
  3. Check if pred is true with the “master” and the current elem.
    • If it is, we need to append elem to the current subgroup.
    • Otherwise, we create a new subgroup with elem as the “master”

As you can see, the code is really messy. I think that I should have created a specific datatype for the subgroup, it could have made pattern matching easier…

Aaaaand that’s it!!! We done it!!! Time for celebration now!

Cute dancing monster