frosch03.de/posts/2013-12-11-HaskellExam
My solution to a Haskell exam
Please keep in mind, that all the source is untested (as it would be in a real exam situation). If there are errors, I'm happy if you let me know.
Exercise 1
Give the type of the following expressions: Just
, Just Nothing
,
Just (Just True)
, Just Just
, Just (Just Just)
I think, the easiest way to do this, is by starting to guess the type of the term in brackets. At least for the more complicated ones.
If the first type is clear, the rest should be straight forward.
Just :: a -> Maybe
Just Nothing :: Maybe (Maybe a) Just (Just True) :: Maybe (Maybe Bool) Just Just :: Maybe (a -> Maybe a) Just (Just Just) :: Maybe (Maybe (a -> Maybe a))
{% endhighlight %}
Exercise 2
Write a function catMaybes :: [Maybe a] -> [a]
, that drop's all the
Nothing
values and only returns a list of the value within the Just
constructor.
Here you only have to parse the list, item by item. If you encounter a
Just
constructor, take the value inside and put it in the list. Than
go on with the rest-list.
catMaybes :: [Maybe a] -> [a] catMaybes [] = [] catMaybes ((Just x):ms) = x : (catMaybes ms) catMaybes (\_:ms) = catMaybes ms
Exercise 3
Write a function mapMaybe :: (a -> Maybe b) -> [a] -> [b]
. The
supplied function produces Maybe
values. If it produces an
Just b
, then put the b
into the result list, otherwise do
nothing.
mapMaybe :: (a -> Maybe b) -> [a] -> [b] mapMaybe f (x:xs) | (Just \_) == f x = x :
| otherwise = mapMaybe f xs
{% endhighlight %}
Exercise 4
Write a function id :: a -> a
, that returns arguments unchanged.
id :: a -> a id x = x
Exercise 5
Write a function null :: [a] -> Bool
, that returns True
, if the list
is emtpy.
null :: [a] -> Bool null [] = False null otherwise = True
Exercise 6
Write a function map :: (a -> b) -> [a] -> [b]
, that applies the
supplied function to all the arguments of the supplied list.
map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : (map f xs)
Exercise 7
Write a function concat :: [[a]] -> [a]
, that tears down a list of
lists, to a simple list.
concat :: [[a]] -> [a] concat (x:xs) = x ++ (concat xs)
Exercise 8
Write a function length :: [a] -> Int
, that returns the length of a
list
length :: [a] -> Int lenght [] = 0 length (\_:xs) = 1 + (length xs)
Exercise 9
Write a function any :: (a -> Bool) -> [a] -> Bool
, that takes a
predicate function, and returns true, if in the list is any element that
supports that.
any :: (a -> Bool) -> [a] -> Bool any \_ [] = False any p (x:xs) | (p x) == True =
| otherwise = any p xs
{% endhighlight %}
Exercise 10
Write a function all :: (a -> Bool) -> [a] -> Bool
, that takes a
predicate function, and returns true, only if all elements of the list
support the predicate function.
all :: (a -> Bool) -> [a] -> Bool all \_ [] = True all p (x:xs) | p x == True = all p
| otherwise = False
{% endhighlight %}
Exercise 11
Write a function concatMap :: (a -> [b]) -> [a] -> [b]
, that supplies
a function to any element of a list, and concat's the results.
concatMap :: (a -> [b]) -> [a] -> [b] concatmap \_ [] = [] concatMap f (x:xs) = f x ++ (concatMap f xs)
Exercise 12
Write a function filter :: (a -> Bool) -> [a] -> [a]
, that returns
only those elements of a list, that support the predicate.
filter :: (a -> Bool) -> [a] -> [a] filter \_ [] = [] filter p (x:xs) | p x == True = x :
| otherwise = filter p xs
{% endhighlight %}
Exercise 13
How to use the filter
function, to get all the strings from a list of
string, that contain an 'a'?
stringsWithA :: [String] -> [String] stringsWithA = map $ filter (== 'a')
Exercise 14
How to use the filter
function, to get a list of lists, where all
lists are longer than 10?
listLonger10 :: [[a]] -> [[a]] listLonger10 = map $ filter $ (>=10) . length
Exercise 15
Give the type of the following expression: filter (=
'a')=,
filter ((>10) . length)
, filter ((=
'a') . head . fst . fst)=
filter (=='a') :: String -> String filter ((>10) . length) :: [[a]] -> [[a]] filter ((=='a') . head . fst . fst) :: (([String], a), b)
Exercise 16
The following data type describes a tree. It's nodes hold lists of values, and it's leafs might hold values.
data Tree a = Br [a] (Tree a) (Tree a) | Lf (Maybe a)
- What is the kind of
Tree
? What is the type ofBr
, and ofLf
?
> :kind Tree Tree :: * ->
> :type Br Br :: [a] -> Tree a -> Tree a -> Tree a > :type Lf Lf :: Maybe a -> Tree a
{% endhighlight %}
The kind is of order 2, because there is only one type needed to produce a second one.
With the type constructor Br
, something of type Tree a
is produced.
In order to do so, a [a]
, and 2 Tree a
are needed.
- Write a function
tree2lst
, that writes all values of a tree into a list.
tree2lst :: Tree a -> [a] tree2lst (Br as lt rt) = (tree2lst lt) ++ as ++ (tree2lst rt) tree2lst (Lf (Just a)) = [a] tree2lst (Lf (Nothing)) = []
- Write an instance of
Eq
forTree a
instance (Eq a) => Eq (Tree a) where (Br as lt rt) == (Br as' lt' rt') | as == as' = (lt == lt') &&
| otherwise = False (Lf a) == (Lf a') | a == a' = True | otherwise = False
{% endhighlight %}
These trees are equal, if there are two branches, with equal lists, and also if both sub-trees are equal, too.
They are also equal, if they are two leafs with equal value, and also if both have no value.
Otherwise, the trees are unequal.
- Write an instance of
Functor
forTree a
instance Functor Tree where fmap f (Br as lt rt) =
fmap f (Lf (Just a)) = (Lf (Just (f a))) fmap _ (Lf Nothing) = (Lf Nothing)
{% endhighlight %}
Ok, Functor
want's you, to write an fmap
function. That function
awaits a function, which is executed on every value inside the tree.
The function is mapped over the list of a branch, and is propagated to the sub-trees via fmap.
The propagation stops at the leafs.
- Write a fold function
foldTree
for theTree a
type
foldTree :: (b -> a -> b) -> b -> (Tree a) -> b foldTree f n (Br as lt rt) =
foldTree f n (Lf (Just a)) = f n a foldTree _ n _ = n
{% endhighlight %}
To fold something means, to tear a structure down. Therefore a function is used, that can consume a value of the tree at a time. That value, together with the result so far, is computed into the next result so far.
For the first calculation, a initial value is also needed.
That initial value is used, in combination with the left sub-tree, to calculate the result so far from the left tree. That result is used, together with the right tree, to calculate the next result so far.
At last, that result is supplied to a fold function for lists, to calculate a final result.
With the help of
foldTree
, write asumTree
function, that sumsall up all values inside the tree.
foldTree (+) 0
If Foldtree
is supplied with the addition function, and 0, the initial
calculation would be a value of the tree plus 0. That is then added to
the next value of the tree, until all values are consumed. The last
result is the final result.
17 With the data type
data Either a b = Left a | Right b
- Write an instance of
Functor
forEither
instance Functor (Either a) where fmap \_ (Left a) = Left
fmap f (Right b) = (Right (f b))
{% endhighlight %}
Again, Functor
needs a definition of the fmap
function. fmap
takes
a function, that transforms an inner value, and a capsuled value. It
executes the function on the inner value of the capsule, and puts the
result back into the capsule.
Keep in mind, that you only have to take action on the Right
side,
because the Left
side is constrained to a
.
- Write an instance of
Monad
forEither
instance Monad (Either a) where (>>=) (Right a) f =
(>>=) (Left x) _ = (Left x) return x = (Right x)
{% endhighlight %}
For Monad
a combination operator and a capsule operator must be
defined. Again, only the Right
side must be calculated, the Left
side is constrained.
The capsuling returns everything supplied also only to the Right
constructor.
18 Write monadic versions for list functions
- Write a monadic version
filterM
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a] filterM f [] = return [] filterM f (a:as) = f a >>= (\n -> let x = filterM f as in if n == True then x >>= (\x'-> return (a:x')) else x)
The function returns just a monadic version of the empty list, if the empty list is supplied.
For all other lists, the predicate function is executed on the first
element of the list. The result is passed into a new function. There a
is added to the result list, only if f a
resulted in true.
- Write a monadic version
mapM
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b] mapM f [] = return [] mapM f (a:as) = f a >>= (\n -> let x = mapM f as in x >>= (\x'-> return (n:x')))\\
The scheme is the same here as in filterM
. The supplied monadic
function is applied to the first element of the list. The calculated
value is then bound, via a lambda expression, to the identifier n
.
mapM
is executed on the rest list. The value n
is than appended to
the result of the inner mapM
.
- Write a monadic version
zipWithM
zipWithM :: (Monad m) => (a -> b -> m c) -> [a] -> [b] -> m [c] zipWithM \_ [] \_ = return [] zipWithM [] \_ \_ = return [] zipWithM f (a:as) (b:bs) = f a b >>= (\n -> let x = zipWithM f as bs in x >>= (\x'-> return (n:x')))
Again, the schema is here exactly the same, as in the previous two. Calculate the first value, and bind it to the rest values.
- Write a function, that applies a function embedded in a monad, to values inside that monad.
ap :: (Monad m) => m (a -> b) -> m a -> m b ap f x = f >>= (\f'-> (x >>= (\x' -> return (f' x'))))
Here, obviously f
must be executed on x
. Therefore both are catched
within the prime variants and are then applied.
The result is then packed back inside the monad.
19 Write text file processing functions
Assume, that there is a test.txt
file in the actual directory.
- Write a program to count the lines within
test.txt
lineNums :: IO Int lineNums = do file <- readFile "/tmp/test.txt" return $ length . lines $ file
The first thing would always be, to read the file into file
. Then the
lines
function splits the file
string into it's lines. After that,
the length
is the line count.
- Write a function, that counts the number of words with more than 2 letters.
countWordsLonger2 :: IO Int countWordsLonger2 = do file <- readFile "/tmp/test.txt" ws <- return $ words file return $ length $ filter ((>2) . length) ws
The file is read. Then the content is split up into words. With filter
only the words, that have a length that is greater 2 (>2) . length
,
are returned. The length of that list is the number of such words.
- Write a function that prints the lines that contain an 'a'
printLinesWithA :: IO () printLinesWithA = do file <- readFile "/tmp/test.txt" ls <- return $ lines file ls' <- return $ filter (foldl fn False) ls putStrLn $ unlines ls' return () where fn \_ 'a' = True fn r \_ = r
Here the file is read and split up into it's lines. With filter, some
lines are filtered out. Which, is determined by a fold, that returns
False
if there is no 'a' within the line, and True
otherwise. These
lines are then printed with putStrLn
.
- Write a function, that returns the longest line, that does not contain an 'a'.
lineContains :: Char -> String -> Bool lineContains c = foldl (\r n -> if n == c then True else r) False
tLongestLineWithoutA :: IO () printLongestLineWithoutA = do file <- readFile "/tmp/test.txt" ls <- return $ lines file ls' <- return $ filter (not . lineContains 'a') ls longest <- return $ foldl (\r n -> if length n > length r then n else r) "" ls' putStrLn longest return ()
First of all, the fold fn
function from 19.3 is here put into a helper
function, called lineContains
. That function takes a character and a
string and returns a boolean value, that indecates, if the char is
within that line.
Then the actual function reads the file, and splits it into lines. These lines are then filtered for lines that do not contain an 'a'.
That result contains all possible lines and another fold is used to break it down to only one line, the longest line.
- Write a function, that returns the number of words of the line with the most words.
longestLineWordCount :: IO Int longestLineWordCount = do file <- readFile "/tmp/test.txt" ls <- return $ lines file putStrLn $ return $ foldl fn 0 (words ls) where fn r n | n > r = n | otherwise = r
Again, a simple fold is used, to return the number of words of the longest line. The fold compares the actual highest number of words, with that of the next line. Only if the next line contains more words, than the actual line, the actual number is taken.
- Write a function, that returns the longest word
longestWord :: IO () longestWord = do file <- readFile "/tmp/test.txt" ws <- return $ words file putStrLn $ return $ foldl fn "" ws return () where fn r n | (length n) > (length r) = n | otherwise = r
Here is the file split into it's words. Then with a fold, only the longest word is passed.