Real World Haskell – Exercises Chapter 8

My answers to the exercises of chapter 8.

Page 205:

2. While filesystems on Unix are usually sensitive to case (e.g. “G” vs. “g”) in file names, Windows filesystems are not. Add a parameter to the globToRegex and matchesGlob functions that allows control over case sensitive matching.

Note that the solution below ignores ranges in character classes.

module GlobRegex
  ( globToRegex
  , matchesGlob
  ) where

import Data.Char (isLower,isUpper,toLower,toUpper)
import Data.List (nub)
import Text.Regex.Posix ((=~))

globToRegex :: String -> Bool-> String
globToRegex cs matchCase
  = '^' : globToRegex' cs matchCase ++ "$"

globToRegex' :: String -> Bool -> String
globToRegex' "" _ = ""

globToRegex' ('*':cs) matchCase
  = ".*" ++ globToRegex' cs matchCase

globToRegex' ('?':cs) matchCase
  = '.' : globToRegex' cs matchCase

globToRegex' ('[':'!':c:cs) matchCase
  = let c' = if matchCase
             then [c]
             else nub (c:shiftCase c:[])
    in "[^" ++ c' ++ charClass cs matchCase

globToRegex' ('[':c:cs) matchCase
  = let c' = if matchCase
             then [c]
             else nub (c:shiftCase c:[])
    in '[' : c' ++ charClass cs matchCase

globToRegex' ('[':_) _ = error "unterminated character class"

globToRegex' (c:cs) matchCase
  = escape c matchCase ++ globToRegex' cs matchCase

escape :: Char -> Bool -> String
escape c matchCase
  | c `elem` regexChars = '\' : [c]
  | matchCase = [c]
  | otherwise = '[' : nub (c:shiftCase c:[]) ++ "]"
  where regexChars = "\\+()^$.{}]|"

charClass :: String -> Bool -> String
charClass (']':cs) matchCase = ']' : globToRegex' cs matchCase
charClass (c:cs) True  = c : charClass cs True
charClass (c:cs) False = c' ++ charClass cs False
  where c' = nub (c:shiftCase c:[])
charClass [] _ = error "unterminated character class"

shiftCase :: Char -> Char
shiftCase c | isLower c = toUpper c
            | isUpper c = toLower c
            | otherwise = c

matchesGlob :: FilePath -> String -> Bool -> Bool
matchesGlob name pat matchCase
  = name =~ globToRegex pat matchCase

Page 212:

1. Glob patterns are simple enough to interpret that it's easy to write a matcher directly in Haskell, rather than going through the regexp machinery. Give it a try.

Note: I forgot to implement negated character classes.

module MatchGlob (match) where

import Data.List (tails)

type Glob = String

match :: Glob -> String -> Bool

match ""  "" = True
match ""  _  = False
match "*" "" = True
match _   "" = False

match (x:xs) z@(y:ys) =
  case x of
    '?'       -> match xs ys
    '\'      -> matchEscape xs z
    '*'       -> matchStar xs z
    '['       -> matchClass xs z
    otherwise -> x == y && match xs ys

matchEscape :: Glob -> String -> Bool
matchEscape [] _ = error "unescaped backslash"
matchEscape _ "" = False
matchEscape (x:xs) (y:ys) = x == y && match xs ys

matchStar :: Glob -> String -> Bool
matchStar x y = or (map (match x) (tails y))

matchClass :: Glob -> String -> Bool
matchClass (x:xs) (y:ys) =
  let (xs',zs) = break (== ']') xs
  in if zs == ""
     then error "unterminated character class"
     else  y `elem` expand (x:xs') && match (tail zs) ys
matchClass _ _ = error "illegal character class"

expand :: String -> String
expand []           = ""
expand (x:'-':[])   = x:'-':""
expand (x:'-':y:zs) = [x..y] ++ expand zs
expand (x:xs)       = x:expand xs

Real World Haskell – Exercises Chapter 4

My answers to the exercises of the fourth chapter.

Page 84:

1. Write your own “safe” definitions of the standard partial list functions, but make sure they never fail.

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

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

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

safeInit :: [a] -> Maybe [a]
safeInit [] = Nothing
safeInit xs = Just (init xs)

2. Write a function splitWith that acts similarly to words but takes a predicate and a list of any type, and then splits its input list on every element for which the predicate returns False.

splitWith :: (a -> Bool) -> [a] -> [[a]]
splitWith p xs =
  let (pre,suf) = break p xs
      suf' = dropWhile p suf
  in pre : case suf' of
             [] -> []
             _  -> splitWith p suf'

Page 97:

1. Use a fold (choosing the appropriate fold will make your code much simpler) to rewrite and improve upon the asInt function from the section called “Explicit recursion” on page 85.

import Data.Char (digitToInt)
import Data.List (foldl')

asInt_fold :: String -> Int
asInt_fold ('-':xs) = -1 * asInt_fold xs
asInt_fold xs       = foldl' step 0 xs
  where
    step :: Int -> Char -> Int
    step x y = 10 * x + digitToInt y

6. Write your own definition of concat using foldr.

myConcat = foldr (++) []

Real World Haskell – Exercises Chapter 3

My answers to the exercises of chapter 3.

Page 60:

1. Write the converse of fromList for the List type: a function that takes a List a and generates a [a].

toList :: List a -> [a]
toList (Cons x y) = x : toList y
toList Nil        = []

2. Define a tree type that has only one constructor, like our Java example. Instead of the Empty constructor, use the Maybe type to refer to a node’s children.

data Tree a = Node a (Maybe (Tree a)) (Maybe (Tree a))
              deriving (Show)

Page 69:

1. Write a function that computes the number of elements in a list. To test it, ensure that it gives the same answers as the standard length function.

myLength []     = 0
myLength (x:xs) = 1 + myLength xs

2. Add a type signature for your function to your source file. To test it, load the source file into ghci again.

myLength :: [a] -> Integer
myLength []     = 0
myLength (x:xs) = 1 + myLength xs

3. Write a function that computes the mean of a list, i.e. the sum of all elements in the list divided by its length. (You may need to use the fromIntegral function to convert the length of the list from an integer into a floating point number.)

mean xs = sum xs / fromIntegral (length xs)

4. Turn a list into a palindrome, i.e. it should read the same both backwards and forwards. For example, given the list [1,2,3], your function should return [1,2,3,3,2,1].

palindrome xs = xs ++ reverse xs

5. Write a function that determines whether its input list is a palindrome.

isPalindrome xs = xs == reverse xs

6. Create a function that sorts a list of lists based on the length of each sublist. (You may want to look at the sortBy function from the Data.List module.)

sortByLength xss = sortBy cmpLength xss
  where
    cmpLength =  xs ys -> compare (length xs) (length ys)

Or, less bulky, using the on function from the Data.Function module.

sortByLength' = sortBy (compare `on` length)

7. Define a function that joins a list of lists together using a separator value.

intersperse :: a -> [[a]] -> [a]
intersperse _ []       = []
intersperse _ (xs:[])  = xs
intersperse s (xs:xss) = xs ++ [s] ++ (intersperse s xss)

8. Using the binary tree type that we defined earlier in this chapter, write a function that will determine the height of the tree. The height is the largest number of hops from the root to an Empty. For example, the tree Empty has height zero; Node "x" Empty Empty has height one; Node "x" Empty (Node "y" Empty Empty) has height two; and so on.

height :: Tree a -> Integer
height (Node _ x y) = 1 + max (height x) (height y)
height Empty        = 0

9. Consider three two-dimensional points a, b, and c. If we look at the angle formed by the line segment from a to b and the line segment from b to c, it either turns left, turns right, or forms a straight line. Define a Direction data type that lets you represent these possibilities.

data Direction = LeftTurn
               | RightTurn
               | Straight
                 deriving (Show)

Real World Haskell – Exercises Chapter 2

My answers to the exercises of the second chapter.

Page 25:

1. What are the types of the following expressions?

False :: Bool
(["foo", "bar"], 'a') :: ([String], Char)
[(True, []), (False, [['a']])] :: [(Bool,[[Char]])]

Page 39:

1. Haskell provides a standard function, last :: [a] -> a, that returns the last element of a list. From reading the type alone, what are the possible valid behaviors (omitting crashes and infinite loops) that this function could have?

From the type alone there is no way to find out what the real type is, neither is there a way to manipulate a value of that type, thus the “only” thing the code can do is return an element of the list.

What are a few things that this function clearly cannot do?

  • Return the size of the list.
  • Reverse the list.

2. Write a function, lastButOne, that returns the element before the last.

lastButOne = head . tail . reverse

Load your lastButOne function into ghci and try it out on lists of different lengths. What happens when you pass it a list that’s too short?

Prelude> lastButOne "a"
*** Exception: Prelude.head: empty list

Real World Haskell – Exercises Chapter 1

Here are my answers to the exercises of chapter 1.

1. Enter the following expressions into ghci What are their types?

Prelude> 5 + 8
5 + 8
13
it :: Integer
Prelude> 3 * 5 + 8
3 * 5 + 8
23
it :: Integer
Prelude> 2 + 4
2 + 4
6
it :: Integer
Prelude> (+) 2 4
(+) 2 4
6
it :: Integer
Prelude> sqrt 16
sqrt 16
4.0
it :: Double
Prelude> succ 6
succ 6
7
it :: Integer
Prelude> succ 7
succ 7
8
it :: Integer
Prelude> pred 9
pred 9
8
it :: Integer
Prelude> pred 8
pred 8
7
it :: Integer
Prelude> sin (pi / 2)
sin (pi / 2)
1.0
it :: Double
Prelude> truncate pi
truncate pi
3
it :: Integer
Prelude> round 3.5
round 3.5
4
it :: Integer
Prelude> round 3.4
round 3.4
3
it :: Integer
Prelude> floor 3.7
floor 3.7
3
it :: Integer
Prelude> ceiling 3.3
ceiling 3.3
4
it :: Integer

2. From ghci, type :? to print some help. Define a variable and then type :show bindings. What do you see?

Prelude> let x = 1
let x = 1
Prelude> :show bindings
:show bindings
it :: Integer = 4
x :: Integer = _
Prelude> x
x
1
Prelude> :show bindings
:show bindings
it :: Integer = 4
x :: Integer = 1

3. The words function counts the number of words in a string. Modify the WC.hs example in order to count the number of words in a file.

main = interact wordCount
  where wordCount input = show (length (words input)) ++ "n"

4. Modify the WC.hs example again, in order to print the number of characters in a file.

main = interact wordCount
  where wordCount input = show (length input) ++ "n"