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.
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:
- Read a file.
- Take the first character of each line.
- 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)
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)
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],]
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]]
In order to do that, we need to:
- Get the current subgroup using last acc.
- Get the first element of this subgroup which is the group “master” using head(subgroup)
- 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!