For each of the following Haskell expressions, write down the result of evaluation.
(i) map (\x->'.')"xyz"
Answer
”…”
(ii) map (\x->[])][]
Answer
[]
(iii) map (\x->[])"abc"
Answer
[[],[],[]] — more tricky than previous case! Don’t accept single empty list here.
(iv) map (\x -> (x,x))"abc"
Answer
[('a','a'), ('b','b'), ('c', 'c')] — i.e. a list of (Char,Char) pairs
(v) let f = \x->x in f (f 1, f 'a')
Answer
(1,a) — since f is polymorphic, and late-bound, although students don’t need to state this explicitly.
1b)
For each of the following Haskell fold expressions, write down the result of evaluation.
(i) foldl (+)0 [1,2,3]
Answer
6
(ii) foldr (+)0 [1,2,3]
Answer
Answer: 6 (same as previous)
(iii) foldl (-)0 [1,2,3]
Answer
Answer: ((0-1)-2)-3 == -6 (calculation not required, just result)
(iv) foldr (-)0 [1,2,3]
Answer
Answer: 1-(2-((3-0))) == 2 (calculation not required, just result)
1c)
Question
Consider a function op :: Int->Int->Int. What property must the op function have, so the results of foldl op and foldr op are equal?
Answer
Students might comment that foldl (+) and foldr (+) have the same results, but foldl (-) and foldr (-) don’t. [0.5] Students might say that op a b must be the same as op b a — not quite, but worth [0.5]. op is associative [2], or (a ‘op’ (b ‘op’ c))== ((a ‘op’ b)‘op’ c) [2]. (max 2)
1d)
Question
(d) Consider function concat :: [[a]] -> [a] which takes a list of lists of elements and returns a list of elements. For instance, concat [[1], [2], [3,4,5]] evaluates to [1,2,3,4,5] and concat ["hello", "world"] evaluates to `“helloworld”. Write a recursive definition for the concat function that uses pattern matching on the list data structure.
Answer
concat :: [[a]] -> [a] -- can omit type since givenconcat [] = [] -- [1]concat (x:xs) = x ++ (concat xs) -- [2]
1e)
Question
(e) Write a point-free definition for the concat function.
Answer
concat :: [[a]] -> [a] -- can omit type since givenconcat = foldr (++) [] -- could be foldl instead [3]-- one mark for fold, one for (++), one for []
Question 2: Monads and Pirate Ships
2a)
Question
What is the type of the monadic bind operator (>>=) in Haskell?
Answer
Monad m ⇒m a → (a → m b)→ m b — 1 mark for type constraint, then 1 for each value type
2b)
Study the following type declarations.
type Treasure = Int — count num. treasure chests
data PirateShip = PirateShip Treasure
— e.g. (PirateShip 2) is ship with two treasure chests
Suppose that a PirateShip can carry a maximum cargo of three treasure chests, without sinking. We want to define a function called addTreasure, which takes a PirateShip parameter and evaluates to a Maybe PirateShip result. If a PirateShip value x currently has fewer than three treasure chests, then addTreasure x evaluates to a PirateShip with one more treasure chest, wrapped in a Just instance. However if the input parameter x already has the maximum three treasure chests, then addTreasure x evaluates to Nothing which denotes that the ship has sunk.
(i) Encode addTreasure as a Haskell function, including its type signature.
Answer
-- [1] for type signatureaddTreasure :: PirateShip -> Maybe PirateShipaddTreasure (PirateShip n) = if n<3 then Just (PirateShip (n+1)) else Nothing-- [1] for each line of function
(ii) Write another function called removeTreasure, which takes a PirateShip as its input parameter. If there is no treasure on the input PirateShip then the result should be a (PirateShip 0) wrapped in a Just instance. If the input PirateShip does have treasure, then the result should be a PirateShip with one less treasure chest on board, wrapped up in a Just instance.
Answer
-- [1] for typeremoveTreasure :: PirateShip -> Maybe PirateShipremoveTreasure (PirateShip n) = Just (PirateShip (if n>1 then (n-1) else 0))-- [3] for function definition
(iii) Now a voyage of a PirateShip can be denoted as a sequence of addTreasure and removeTreasure function calls. Show how you would encode such a sequence in Haskell, using the monadic bind operator.
Answer
Start with an empty pirate ship, wrapped in a Just. i.e. Just (PirateShip 0). [1] Then use bind to sequence [1] addTreasure and removeTreasure calls. e.g. [1]
Just (PirateShip 0) >>= addTreasure >>= addTreasure >>= removeTreasure
(iv) What is the value of such a sequence if more than three consecutive addTreasure calls appear in the sequence?
Answer
Nothing. [2]
(v) Suggest an alternative higher-level syntactic construct in Haskell to encode the voyage of a PirateShip.
Answer
Ideally the do construct [1] which is syntactic sugar [1] for a sequence of binds. Give an example [1] like
do x <- Just (PirateShip 0) y <- addTreasure x z <- removeTreasure y return z
I would give appropriate credit for other solutions, like using forM_ or sequence_ from the Control.Monad library.
Question 3: Binary Trees and Twistable Typeclasses
3a)
Abstract
Here is a definition of a binary tree datatype in Haskell.
data Tree a = Leaf | Node a (Tree a) (Tree a)
Question
Write a function countNodes :: Tree a -> Int that returns the number of Nodes in a binary tree. For instance, countNodes (Node 1 Leaf (Node 1 Leaf Leaf)) evaluates to 2.
Answer
countNodes :: Tree a -> Int -- could miss out type since givencountNodes Leaf = 0 -- base case [1]countNodes (Node _ t1 t2) = 1 + countNodes t1 + countNodes t2-- [1] for pattern match-- [1] for recursion-- [1] for correct addition
3b)
Question
Write a function mirror :: Tree a -> Tree a that returns the mirror image of a binary tree.
Answer
mirror :: Tree a -> Tree a -- can omit type, since givenmirror Leaf = Leaf -- [1]mirror (Node x t1 t2) = Node x (mirror t2) (mirror t1)-- [1] for pattern match, [1] for recursive call-- [1] for correctness
3c)
Abstract
Here is a definition of a typeclass in Haskell. We will use this typeclass to characterize
finite data structures that we can reverse, in some sense.
class Twistable t where
twist :: t→t
size :: t→Int
(i) Now express that Tree a is an instance of the Twistable typeclass, with reference to the countNodes and mirror functions you have already defined.
Answer
instance Twistable (Tree a) where twist = mirror size = countNodes-- 1 mark per line
is an instance of the Twistable typeclass.
Answer
instance Twistable ([a]) where twist = reverse size = length-- 1 mark per line
(iii) Here are the properties that twist must satisfy:
If the data to be twisted has size 0, then the twisted data has the same value as the original data.
The twisted data always has the same size as the original data.
Twist applied twice (i.e. twist (twist x) ) is equal to the identity function.
Now express these three properties as quickcheck specifications for arbitrary lists [a], with appropriate type signatures.
Answer:
-- 1.quickCheck((\x-> if (size x) == 0 then (twist x)==x else True)::(Eq a)=>[a]->Bool)-- 2.quickCheck((\x-> if (size x) > 0 then size(twist x)==(size x) else True)::[a]-> Bool)-- 3.quickCheck((\x-> twist(twist x)==x)::(Eq a)=>[a]->Bool)-- [2] marks per spec. Partial marks for nearly correct solutions
determine the values of the following expressions.
(i) f 1 + 1
Answer
3 (because function application binds tighter than plus)
(ii) f (1+1)
Answer
4 (evaluate expr in parentheses first)
(iii) f $ 1+1
Answer
4 (evaluate rhs of dollar first)
(iv) (f 1)+ 1
Answer
3 (evaluate parens first)
(v) (+)(f 1)$ f 1 + 1
Answer
5 (== plus 2 (2+1))
1b)
Assume that f has type Int->[Int], g has polymorphic type a->a, and h has polymorphic type [a]->a. Determine the types of the following expressions.
(i) g (f 1)
Answer
[Int]
(ii) f (g 1)
Answer
[Int]
(iii) (h.f)42
Answer
Int
(iv) h (g (f 1))
Answer
Int
(v) g h
Answer
[a] -> a
1c)
Given the integer exponentiation operator ^, i.e. x^y evaluates x raised to the power y, then calculate the values of the following expressions.
(i) [2^x|x<-[3,1,4]]
Answer
[8,2,16]
(ii) map (\x->2^x)[1..3]
Answer
[2,4,8]
(iii) foldl (\x y -> x^y)1 [2,3]
Answer
1
(iv) foldr (\x y -> x^y)1 [2,3]
Answer
8
(v) foldr (\x y -> (2^x):y)[] [1,2,3]
Answer
[2,4,8]
1d)
(i) Outline two differences between lists and tuples in Haskell.
Answer
Any two of:
syntax - square versus round brackets
shape - lists can have any number of elements, tuples have fixed number
type - list elements have uniform type, tuple elements can be different
polymorphic list functions generalise over all lists, whereas n-tuple functions (fst, etc) are specialized for particular n
(ii) Define a function allpairs :: [a] -> [b] -> [(a,b)] that computes the Cartesian product of two lists, i.e. the list of all pairs of elements. For instance, allPairs "hi"[1,2,3] should evaluate to [('h', 1), ('h', 2), ('h', 3), ('i', 1), ('i',2), ('i', 3)].
Examine the following Haskell definitions, which are relevant for this question.
data Dragon a = LiveDragon [a] | DeadDragon
— list of a’s is record of food items eaten by dragon
class Food f where
poisonous :: f → Bool
— True means it’s bad to eat
2a)
Question
Define a function eats :: Food a => Dragon a -> a -> Dragon a which, if the first parameter is a LiveDragon, checks whether the second parameter (the food) is poisonous—if it is, then the result is a DeadDragon. > If the food is not poisonous, then the result is a LiveDragon, with the food parameter value appended to the end of the LiveDragon’s list of food. On the other hand, if the first parameter is a DeadDragon, then the result should also be a DeadDragon, whatever food is supplied as the second parameter.
Answer
eats :: Food a => (Dragon a) -> a -> (Dragon a)eats (LiveDragon xs) x = -- 1 if (poisonous x) -- 1 then DeadDragon -- 1 else (LiveDragon (xs++[x])) -- 1eats DeadDragon _ = DeadDragon --1
2b)
Question
Declare the Int data type to be an instance of the Food type class. The only poisonous integer value is 13.
Answer
instance Food Int where -- 2 poisonous x = (x==13) -- 1
2c)
What code should be added to enable a Dragon to eat other Dragons? Assume a LiveDragon may eat a LiveDragon, but if a LiveDragon tries to eat a DeadDragon, it's poisonous, so the LiveDragon itself will become a DeadDragon.
Answer
The only code required is a type class instance spec for Dragon:
You have been given a vague specification:
The Republic of Ruritania is migrating its car number plate format from a sequence of five alphanumeric digits to an encoding based on:
a) the locality of registration, that is the state and district the car was registered in;
b) year and month of registration; and finally
c) a random sequence of three alphanumeric digits. Ruritania has five states, North, South, East, West, and Capital with short codes of: N, S, E, W, and C.
Within each state there are twenty districts each known by its number. You can assume that alphanumeric digits are digits that are either numbers in the range zero to nine, or uppercase characters from the English alphabet.
(i) Write some Haskell code to represent both registration formats as a single algebraic data structure called CarReg.
Answer
Application
1 mark for reasonable representation of an alphanumeric digit.
1 mark for a reasonable representation of the old format using a five element tuple, or data constructor with five parameters.
3 marks for a reasonable representation of the new format, distributed as:
1 mark for representing locality of registration.
1 mark for representing years and months.
1 mark for representing random sequence.
Possible solution:
data AlphaNumeric = Digit Int | Letter Char deriving (Eq, Show)data State = North | South | East | West | Capital deriving (Eq, Show)data CarReg = OldFormat AlphaNumeric AlphaNumeric AlphaNumeric AlphaNumeric AlphaNumeric | NewFormat State Int Int Int [AlphaNumeric] deriving (Eq, Show)
(ii) Discuss how robust your definition of CarReg is from incorrect registration numbers in both the old and new car registration formats. Further, describe how you would use Haskell's module and type system to ensure only correct registrations can be constructed and incorrect registrations reported.
Answer
Synthesis/Recall:
1 mark for discusson of how robust the old format is.
2 mark for discussing how robust the new format representation is.
1 mark for describing how the module system can be used to encapsulate implementation details, and use smart constructors to control how car registrations are implemented.
1 mark for describing the use of Haskell’s Maybe or Either types to capture error.
3c)
Given the following type signatures for two functions operating on lists:
— Concatentates two lists together.
append :: [a] → [a] → [a]
— Takes the first ‘n‘ elements from a list.
take :: Int → [a] → [a]
(i) Demonstrate how you would use the QuickCheck library to determine if the append function was working as intended.
Answer
Recall/Application
1 mark for a valid QuickCheck specification.
1 mark for a specification that tests appending of lists.
(ii) In the future, Haskell will support Dependent Types, allowing types to depend on values. Discuss how you can improve the definition of take using dependent types to ensure that it works as intended.
Answer
Synthesis:
1 mark replacing the list definition with a list type indexed by the length of the list.
1 mark length of the list is encoded using a peano representation.
1 mark generating an invariant that the resulting list is the same length as the two input lists.
Possible solution:
-- With dependent types, we could define:data Vec :: Nat -> * -> * where Nil :: Vec Zero a Cons :: a -> Vec n a -> Vec (Succ n) atake :: (n <= length xs) => SNat n -> Vec m a -> Vec n a
Consider a function pairToList that takes a tuple of two elements with identical types, and returns a list containing the same two elements. Give the full definition of this function in Haskell, including its type signature.
Now consider a function listToPair :: [a] -> Maybe (a,a) that takes a list of two elements and returns a pair containing the same two elements.
(i) Why is the return value wrapped as a Maybe instance?
Answer:
Input list may not have precisely two items, so we can’t always return a pair
Need to return an error value for some inputs - Nothing
(ii) Give the Haskell definition for the listToPair function
Answer:
listToPair [x,y] = Just (x,y)listToPair _ = Nothing
(iii) Briefly describe a refinement to the type system that would enable a programmer to specify that the listToPair function will only handle lists with two elements.
Answer:
Dependent types, which would allow us to do simple integer arithmetic on list lengths statically.
1c)
Question
In some ways, listToPair and pairToList are dual functions. Write a simple QuickCheck boolean property to specify this duality.
Answer:
prop_dual x = (listToPair (pairToList x)) == (Just x)
1d)
Question
Study the Haskell expression below. Explain what it means, and what the resulting value will be.
(fmap.fmap) (+1) (Just (1,2))
Answer:
(+1) is the increment function
Just (1,2) is a pair of numbers wrapped in a Maybe instance
The double fmap allows us to apply the increment function inside the nested structure (a nested functor - tuple inside Maybe)
The result is Just (1,3) (not Just (2,3))
1e)
Question
Suppose a Haskell program contains the following definitions:
n = 3 + nxs = [if x==3 then n else x | x <- [0..5]k = length xs
Give the values of n, xs, and k. For any undefined value, write bot (for bottom). If k is bot, explain why; if it is not, explain how its value is computed.
Answer:
The value of n is bottom (undefined) because the addition requires the value of n, and this cannot be computed before the addition is performed. Thus n has a circular definition that cannot be computed. The list xs has the value [0, 1, 2, bot, 4, 5]; thus the list itself is not bottom, but one of its elements is bottom. The value of k is 6, which is defined because the length function only examines the list structure and does not need to use any of the values in the list.
1f)
ABSTRACT
Consider a list posInts:: [Int] with value [1, 2, 3, …] which is infinitely long.
(i) Write a definition of posInts.
Answer:
posInts :: [Int]posInts = let f x = x : f (x+1) in f 1
(ii) Explain how lazy evaluation makes it possible to define infinite data structures like posInts.
Answer:
Without lazy evaluation, the expression f 1 would never return a value; it would go into an infinite loop filling up memory with the list. Lazy evaluation causes a closure to be returned, which is a representation of how to compute more of the result when needed. Only as much of the list will be constructed as actually needed by the program.
Question 2:
Abstract
Haskell’s type inference allows type signatures of Haskell functions to be calculated automatically from a function definition. For each of the following definitions, provide an appropriate, general type signature for func.
2a)
Question
func xs = map (+1) xs
Answer:
func :: Num a => [a] -> [a]
with 1 mark for [a]→[a] and 1 mark for Num constraint. The following will get
only 1 mark:
func :: [Int] -> [Int]
2b)
Question
func x = do
let n = x * 2
let xs = replicate n ’*’
putStrLn xs
Answer:
func :: Int -> IO ()
with 2 marks for IO () and 1 mark for Int →. Deduct 0.5 marks for:
func :: Num a => a -> IO ()
since replicate requires an integer parameter.
replicate :: Int -> a -> [a]
2c)
Question
func f xs = map show (filter f xs)
Answer:
func :: Show a => (a -> Bool) -> [a] -> [String]
• 1 mark: f is a function that returns a Bool, from the type of filter.
• 1 mark: the second argument is a list
• 1 mark: for recognising that show implies use of the Show typeclass and that
also means the function return type includes a String.
• 1 mark: for recognizing that the map means the function return value is a list of
Strings
2d)
Question
func f g x =
let res = g x
in
case res of
Left e → Left (f e)
Right r → Right r
Answer:
func :: (a -> b) -> (s -> Either a r) -> s -> Either b r
We are looking for:
• 0.5 mark: if there are five arrows in the type
• 0.5 mark: if there are three top-level parameters and one result
• 1 mark: The function f::a->b converts the error returned by g to one returned
by f.
• 2 marks: The function g maps the type of x to an Either type
• 1 mark: there is a correspondence between the return type of g and the overall
return type, with the appropriate Left component type changed according to the
mapping in f
2e)
Question
func xs ys = zipWith f xs ys
where
f x y = do
x’ ← h x
y’ ← h y
pure (x’, y’)
h [] = Nothing
h (x:_) = Just x
Answer:
func :: [[a]] -> [[b]] -> [Maybe (a,b)]
Where:
• 1 mark: Both xs and ys are lists of lists with different polymorphic types.
• 2 marks: The function f dictates the element type of the result of zipWith
i.e. a list of Maybe pairs of elements from xs and ys. Here f is a red herring,
Maybe is a monad!
• 1 mark: The type of zipWith means we are returning a list.
2f)
Question
By default, type signatures are optional in Haskell source code. The GHC compiler has an
option -fwarn-missing-signatures that warns programmers if a top-level function
body has a missing signature.
Give one advantage and one disadvantage of this default behaviour.
Answer
• 1 mark for a reasonably described advantage, for example, sometimes you don’t
know the type
• 1 mark for a reasonably described disadvantage, for example, gap between
mental model and actual model
Question 3:
Abstract
Acme University has the following percentage/grade/point mappings for student assessments:
Percentage
Letter Grade
Grade Point
100− 85
A
5
84− 74
B
4
73− 63
C
3
62− 52
D
2
51− 41
E
1
40− 0
F
0
3a)
Question
Write a suitable Haskell data type, called LetterGrade, to represent letter grades A–F
inclusive.
Answer
Ideally we are looking for an enumerated types such as:
data LetterGrade = A | B | C | D | E | F
— 1 mark for data keyword
— 1 mark for sum type with six values
Award no more than 0.5 marks for a String encoding!
3b)
Question
Define a Haskell function, including type signature, that calculates an integer grade point
value from a LetterGrade value.
Answer
Solution: We are looking for a simple function that uses pattern matching of
LetterGrade data constructors to a grade point, encoded as an Int.
• 1 mark: for type signature LetterGrade → Int
• 1 mark: for pattern matching
• 1 mark: for correctness
3c)
Question
Define a Haskell function, including type signature, that calculates a LetterGrade value
from an integer percentage.
Answer
• 2 marks: Suitable type signature that encapsulates possible error: Either or
Maybe.
• 1 mark for use of guards or similar construct for catching the ranges of percent-
ages.
• 1 mark for appropriate error handling.
• 1 mark: correctness.
3d)
Question
A grade point average (GPA) is computed as the average (i.e. arithmetic mean) of a list of
grade point scores. Grade point scores are integer values. The GPA is a real number, i.e. a
floating-point value. Define a function gpa, including an appropriate type signature, that
takes a list of grade point scores and, where possible, returns a GPA value. Pay attention to
potential error cases.
Typeclasses provide a means to write parametric code and ensure that polymorphic functions
can utilise common functionality. Outline, perhaps using Haskell code fragments, how
you might construct a typeclass that allows you to represent marks as any of letter grades,
percentages, and grade points. Explain how typeclass definitions can reduce boilerplate
code.
Answer
1 mark: need to define conversion functions between the various mark represent-
ations, like the letter grade to point conversion function above
• 1 mark: have a canonical representation (perhaps grade points?) and convert each mark into this canonical representation when processing.
• 1 mark: The typeclass will specify a toGradePoint function
• 1 mark:Sensible definition e.g. class Mark a where toGradePoint :: a -> Int
• 2 marks: Using a canonical representation means we can do all calculations in
this form, reducing number of functions and cases to consider … conversion
functions mean we do implicit conversions to avoid excess duplicated conversion
code.
• max 4 marks
What are higher-order functions and why are they so common in Haskell?
Answer
A higher-order function takes other functions as parameters (0.5 mark) and/or returns other functions as results (0.5 mark). Higher-order functions are a natural consequence of the functional programming paradigm (0.5 mark) in which functions are first-class citizens (0.5 mark), i.e., functions are values (0.5 mark).
1b)
Question
Give the type signature and an explanation of the behaviour of the foldr higher-order function on lists.
Answer
foldr :: (a -> b -> b) -> b -> [a] -> b
The foldr function takes a binary operation, an initial value, and a list. It applies the binary operation to each element of the list and the result of folding the rest of the list, starting from the right with the initial value.
1c)
Question
Define a Haskell function twice :: (a->a)-> a -> a which applies the function (supplied as first parameter) two times to the subsequent parameter value. For instance, twice (+1) 0 would evaluate to 2.
Answer
twice f x = f (f x)
1d)
Question
Now consider how to generalize the function twice to a function ntimes, which repeatedly applies a function (supplied as a parameter) multiple times. The type signature is: ntimes :: (a->a) -> Int -> a -> a. For example, ntimes (+2) 5 0 evaluates to 10 and ntimes tail 3 [1..4] evaluates to [4]. Write a Haskell definition for this ntimes function. You may assume the supplied integer value is non-negative. If the integer value is 0 then the behaviour is same as the identity function.
Answer
-- base case, identity functionntimes f 0 x = x-- recursive casentimes f n x = ntimes f (n-1) (f x)
ntimes :: (a->a) -> Int -> a -> antimes f 0 x = x ntimes f n x = ntimes f (n-1) (f x)
1e)
Question
Study the blowup function definition below. What happens if we evaluate blowup 42 at the GHCi interactive prompt, and why?
blowup :: Integer -> Integerblowup n = n + blowup $ n+1
Answer
The evaluation does not terminate (1 mark)
It causes infinite recursion - no base case (1 mark)
Eventually we get a stack/heap overflow error due to recursion, memory exhaustion (1 mark)
1f)
Question
Now suppose we evaluate ntimes blowup 0 42 at the GHCi interactive prompt. Explain why this evaluation terminates immediately with the result 42.
Answer
ntimes with 0 value is the identity function (1 mark)
the higher order function ntimes never needs to apply the blowup function to anything - it stays unevaluated (1 mark)
there is no recursion involved - simple rewrite / base case (1 mark)
Question 2: Maps (Dictionaries) and Types
2a)
What is the role of =>, i.e. the'fat arrow' notation, in a Haskell type? Explain using a simple example if necessary.
Answer
It is a typeclass (0.5 mark) constraint (0.5 mark). It expresses some behavioural constraint on the relevant polymorphic type variable (1 mark). For example, Eq a => a -> a means that the type denoted by a must be an instance of the Eq typeclass (1 mark).
2b)
Question
Consider using a list of (key, value) pairs in Haskell to represent an associative data structure, like a Python dictionary. Given generic type variables k for the key type and v for the value type, a potential type for the lookup function is given below: lookup :: [(k,v)] -> k -> Maybe v (i) Which constraint should we add to the lookup function type, and why?
Answer
To allow key comparisons (1 mark) we would prepend typeclass constraint Eq k => (1 mark) to the type. Also accept Ord for comparisons.
(ii) Why is the return type for the lookup function wrapped up as a Maybe value?
Answer
We may lookup a key that is not present in the list of pairs
The Maybe a type extends the set of possible values by one more than the original set of values in a.
The appropriate value to return for missing keys is Nothing, which indicates the absence of a value
(iii) Write a Haskell definition for the lookup function, ideally using the function type you defined in part (i) above.
Answer
lookup :: Eq k => [(k,v)] -> k -> Maybe vlookup [] x = Nothinglookup ((x,val):rest) y = if x == y then Just val else lookup rest y
(Accept alternative solutions with pattern matching etc)
lookup :: Eq k => [(k,v)] -> k -> Maybe vlookup myList key = if res then Just $ snd $ head res else Nothing where res = filter (\(k, _) -> k == key) myList
2c)
Question
Why might a list of tuples be an inappropriate representation for an associative data structure like a dictionary?
Answer
Linear search time in size of dictionary
Other operations likely to be O(n) too.
No constraints to prevent repeat keys stored with diff values
Accept other reasonable answers.
2d)
Question
How would you define a type alias in Haskell for the [(k,v)] data structure when it is used to implement a dictionary?
Answer
type Dictionary k v = [(k,v)]
Question 3: Monads
3a)
Question
Give the precise type for the bind function (>>=) in the Comp monad.
Answer
(>>=):: (Comp a)-> (a -> Comp b)-> (Comp b)
1 mark for each subtype in brackets (x3), and 1 mark for correct ‘shape’ with function arrows. Only 1 mark overall for the general bind type (in terms of Monad m rather than Comp).
3b)
Abstract
Suppose you are given two functions: primes :: Int -> Int and fibs :: Int -> Int which compute the nth prime number and nth fibonacci number respectively. These are compute-intensive, long-running functions for large values of n.
(i) Imagine a function funnysum which takes an Int value n then computes the sum of the nth prime and the nth Fibonacci number, returning this sum value in the Comp monad. What is the type signature of the funnysum function?
Answer
funnysum :: Int -> Comp Int
(ii) Write a Haskell definition for the funnysum function using a single do block.
Answer
funnysum n = do let a = primes n let b = fibs n return (a+b)
(iii) How would you map the funnysum function over a list of integers to generate a corresponding list of funnysum result values? Further, what is the type of your result list?
Answer
Several alternatives possible:
map funnysum gives [Comp Int].
mapM funnysum gives Comp [Int].
Either of the above with the appropriately sequenced runComp call(s) gives [Int].
(iv) Suppose we have an Int value in the IO Monad, perhaps the result of user input. How would we provide this IO Int value as input to the funnysum function? You may answer with Haskell source code and/or a general description of your approach.
Answer
funnysum takes Int but we have IO Int
we would need to be in IO Monad, e.g. in a do block, when we call funnysum
multiple monads - we end up in IO (Comp Int) or similar
perhaps use a monad transformer stack? Would need an CompT transformer to be defined
3c)
Question
Discuss possible motivations for using the Comp monad.
Answer
We might use Comp for long-running computations, perhaps executing them in parallel on different threads. This is like Futures or Promises in other languages.
Comp allows ordering of computations, controlled sequencing. It is precisely a ‘strict identity monad’.
What is parametric polymorphism and why is it so common in Haskell?
Answer
Like generics in Java (0.5 marks) we can use type variables (0.5 marks) in Haskell to represent unconstrained, or arbitrary (0.5 marks) types for values. Effectively type variables are universally quantified (0.5 marks). It allows us to maintain static type safety (0.5 marks) while expressing computation in a generic way (0.5 marks), regardless of actual types (0.5 marks) max of 1.5 marks for the definition.
Due to Haskell’s powerful type inference system (0.5 marks), we can resolve type variables at compile time. The functional nature of Haskell (0.5 marks) and its widespread use of container data structures (0.5 marks) make polymorphism an efficient way to code generically. max of 1.5 marks for the explanation.
1b)
Abstract
Consider the Haskell functions with their type signatures listed below. For each function, give an actual function definition that would conform to the type signature shown. In each case, also give a short documentation-style explanation of the behaviour for the particular function you define.
(i) f :: a -> a
Answer
f x = x — identity function
(ii) g :: a -> b -> a
Answer
g x y = x — selector, return first of two parameters
(iii) h :: a -> [b]
Answer
h _ = [] — returns empty list for any input
(iv) i :: a -> b
Answer
i x = i x — bottom, infinite recursion (also accept function that throws an error)
1c)
Abstract
Now consider this Haskell function:
twice f x = f $ f x — apply f two times to x
(i) Give the Haskell type of the function twice and explain how you have inferred this type.
Answer
Type is twice :: (a->a)->a->a (0.5 marks, brackets must be correct). f is a function a→a that takes an arbitrary value (1 mark), we know its param has the same type as its result since the result of the first invocation becomes the param of the second invocation of f (1.5 marks)
(ii) Give the Haskell type of the expression (twice.twice) and explain how you have inferred this type.
Answer
Type is same as above — (a->a)->a->a (0.5 marks, brackets must be correct).
(.) is function composition, with type (b -> c)-> (a -> b)-> a -> c (0.5 marks)
now sub in type variables (0.5 marks for attempt)
a -> b has to match the type of twice, (t->t)->t->t, so a becomes t->t and b becomes t->t (1 mark)
same for b->c (0.5 marks)
so basically, a->c now becomes (t->t)->(t->t) which is just the original type (t->t)->t->t (0.5 mark)
same type, but function f will be applied four times now instead of twice… (0.5 marks)
(a->a)->a->a(a->a) -> (a->a) -> a -> a(a->a) -> a -> a
Question 2: Lists and Lazy Evaluation
Abstract
This question is about lists in Haskell. Note that, where you are asked to produce source
code, minor syntax errors will not be penalised so long as your intention is clear and correct.
2a)
Question
Give a Haskell definition for an infinite list of strings, yn :: [String], where every even indexed element of the list is the String value “yes” and every odd indexed element of the list is the String value “no”. Try to ensure your definition of yn is concise.
Answer
yn = "yes":"no":yn
Accept alternative solutions like: concat $ repeat ["yes","no"]
2b)
Question
Briefly discuss (in 50 words or fewer) the features of the Haskell language that enable the definition of such an infinite data structure.
Answer
Recursion, recursive definitions or recursive data structures
Laziness, lazy evaluation
2c)
Question
Give a QuickCheck property specification p::Int->Bool that could be used to test that odd indexed elements of list yn are equal to the String value “no”.
Answer
p = \x -> if x `mod` 2 == 1 && x > 0 then yn!!x == "no" else True
Lose 1 mark if part of compound condition for x is missing. 3 marks for correct solution.
2d)
Question
Will a test run of quickCheck p actually terminate? If so, why does it terminate? If not, why not?
Answer
Yes it will terminate (1 mark). Even though the list is infinite, QuickCheck only runs a finite number of test cases (1 mark).
2e)
Question
Give the Haskell definition for a function butter :: String -> String which appends the String value " but " onto its argument. For instance butter "head" will evaluate to "head but ".
Answer
butter x = x ++ "but "
2f)
Question
Now give the Haskell definition for a function uncertainty :: [String] -> String where the invocation uncertainty yn will evaluate to the String value "yes but no but yes but no but". You should use appropriate functions from the Haskell Prelude, along with the butter function from above.
Answer
uncertainty x = concat $ take 4 $ map butter x
1 mark for each Prelude function (x3), 1 mark for correct parameters, 1 mark for correct ordering / nesting of calls.
2g)
Question
Observe that the infinite list yn only contains two distinct values. Is it possible for a Haskell list to contain an infinite number of distinct values? If so, give an example list. If not, explain why not.
Answer
Yes, in principle infinite lists can all have distinct values
Although we will only ever compute a finite number of them due to the finite stack size, and the limited time
An example might be the Integer list [1..] or the list xs = “x”:(map (‘x’:) xs)
Question 3: Email API and Monads
Abstract
This question involves a scenario about sending emails. Some of the APIs are real, whereas
others are fictitious. You have encountered the real APIs throughout the course. The
question provides you with all the relevant documentation required to make use of the
fictitious APIs. Note that developing compilable Haskell code will simply take too long.
Minor syntax errors will not be penalised.
First, study the data type definition below. This is used to represent email addresses.
data Address = Address {
user :: String,
domain :: String
}
3a)
Question
Write Haskell source code to turn an arbitrary Address value into an appropriate String value, by means of implementing the Show typeclass. The generated String value should concatenate the user and domain values, with an ’@’ character in between them.
instance Show Address of show (Address user domain) = user ++ "@" ++ domain
Answer
instance Show Address where show (Address u d) = u ++ "@" ++ d
3b)
Question
Write Haskell source code, including an appropriate type signature, for getAddressFromUser, which prompts the user for a String value via interactive keyboard input and produces the appropriate Address value, if the input is valid. For the purposes of this question, you may assume that valid user input would be a String including a single ’@’ character, which is neither the first nor the last character. Your code should handle invalid input in a reasonable, idiomatic fashion for Haskell.
Note that you may make use of the splitOn API function:
splitOn
:: String — String to split on, error if empty
→ String — Input text
→ [String] — Result
— break input text String into pieces
— separated by the first String
— argument, consuming the delimiter.
getAddressFromUser:: IO (Maybe Address)getAddressFromUser = do input <- getLine parts = splitOn "@" input if length parts /= 2 then return Nothing else return $ Just $ Address (parts !! 0) (parts !! 1)
Answer
getAddressFromUser :: IO (Maybe Address)getAddressFromUser = do s <- getLine let parts = splitOn "@" s if length parts /= 2 then return Nothing else return $ Just $ Address (parts !! 0) (parts !! 1)
(accept alternative solutions, such as throwing exception or using Either)
3c)
Question
Now consider the following API functions:
mkMail
:: Address — sender email address (from)
→ Address — recipient email address (to)
→ String — email subject
→ String — email body
→ Email — result
sendMail
:: String — hostname of smtp server
→ String — username for login
→ String — password for login
→ Email — message to send
→ IO() — result
Why does the sendMail function have an IO result but the mkMail function does not have an IO result?
Answer
mkMail simply constructs a pure data structure, probably a bytestream in memory, no interactions with outside world (1 mark)
sendMail communicates with the SMTP server, which is a visible side-effect (1 mark)
3d)
Question
Study the following specification carefully, then write Haskell code to meet this specification.
You may find it helpful to use higher-order list functions like map, mapM, mapM_, or similar.
Your task is to produce an implementation of the function sendEmails :: [Address] -> IO (). This function prompts the user for input with a question:
‘what is your email address?’ then receives an answer using getAddressFromUser
as defined above. If the input email address is not valid, the function sets the sender
address to “noreply@gla.ac.uk”. Now the code should construct an email to
send to each of the addresses in the list supplied as the parameter of the sendEmails
function. Each of these emails should come from same sender address. Each of these
emails should have a subject set to the String value “Hello” and a body set to the
String value “I hope your exam is going well.”. The emails should all
be sent via the server smtp.gla.ac.uk with username foo and password secret.
There are lots of possible solutions to this exercise. Marks will be awarded for correctness,
as well as for idiomatic, elegant functional code.
sendEmails :: [Address] -> IO ()sendEmails emailList = do putStrLn "What is your email address?" givenAddress <- getAddressFromUser let userAddress = case givenAddress of Nothing -> (Address "noreply" "gla.ac.uk") (Just a) -> a let emails = map (\x -> mkEmail givenAddress x "Hello" "I hope your exam is going well") emailList mapM_ (\x -> sendMail "smtp.gla.ac.uk" "foo" "secret" x) emails
Answer
sendEmails :: [Address] -> IO ()sendEmails addresses = do putStrLn "what is your email address?" myAddress <- getAddressFromUser let fromAddress = case myAddress of Nothing -> (Address "noreply" "gla.ac.uk") (Just a) -> a let emails = map (\x -> mkMail fromAddress x "Hello" "I hope your exam is going well.") addresses forM_ emails (sendMail "smtp.gla.ac.uk" "foo" "secret") -- 1 for forM_ or similar iteration over emails
— 1 for sendMail with args
— 2 for point-free, lets, and/or other elegant style
What is the role of ⇒, i.e. the ‘fat arrow’ notation, in a Haskell type? Explain using a simple example if necessary.
Answer
It is a typeclass (0.5 mark) constraint (0.5 mark). It expresses some behavioural constraint on the relevant polymorphic type variable (1 mark). For example, Eq a => a -> a means that the type denoted by a must be an instance of the Eq typeclass (1 mark).
1b)
Abstract
(b) Consider using a list of (key, value) pairs in Haskell to represent an associative data structure, like a Python dictionary. Given generic type variables k for the key type and v for the value type, a potential type for the lookup function is given below:
lookup :: [(k,v)] -> k -> Maybe v
(i) Which constraint should we add to the lookup function type, and why?
Answer
Answer: To allow key comparisons (1 mark) we would prepend typeclass constraint Eq k => (1 mark) to the type. Also accept Ord for comparisons.
(ii) Why is the return type for the lookup function wrapped up as a Maybe value?
Answer
We may lookup a key that is not present in the list of pairs
The Maybe a type extends the set of possible values by one more than the original set of values in a.
The appropriate value to return for missing keys is Nothing, which indicates the absence of a value
(iii) Write a Haskell definition for the lookup function using the function type you defined in part (i) above and using a higher-order list processing function.
Answer
lookup :: Eq k => [(k,v)] -> k -> Maybe vlookup l k = let res = filter (\(k', _) -> k == k') l in if null res then Nothing else Just $ snd (head res)
(Accept alternative solutions with fold but with a lower mark)
1c)
Question
Why might a list of tuples be an inappropriate representation for an associative data structure like a dictionary?
Answer
Linear search time in size of dictionary
Other operations likely to be O(n) too.
No constraints to prevent repeat keys stored with diff values
Accept other reasonable answers.
1d)
Question
How would you define a type alias in Haskell for the [(k,v)] data structure when it is used to implement a dictionary?
Answer
type Dictionary k v = [(k,v)]
Question 2: Algebraic Datatypes and Type Inference
(i) Type checking this code gives a type error. What is the reason for the type error?
Answer
The type variable ‘a’ can only be bound to one concrete type. The types of [],[(3,[])],[(5,[(6,[])])] are not the same.
(ii) Create an algebraic datatype Tree1 with constructor T1 such that, when applying the type constructor T1 to the expression bound to tree1, tree1 is of type Tree1. You can assume that the integers are of type Int. The type Tree1 is not polymorphic.
data Tree1 = T1 (Int, [( Int , [(Int, [(Int, [Int] )] )] )] )
Answer
data Tree1 = T1 (Int,[(Int,[(Int,[(Int,[Int])])])])
(iii) Write an expression res = ... that computes the sum of all values stored in tree1.
Answer
res = let (v0,[(v1,[]),(v2,[(v3,[])]),(v4,[(v5,[(v6,[])])])]) = tree1 in v0+v1+v2+v3+v4+v5+v6
res = let (v0,[(v1,[]),(v2,[(v3,[])]),(v4,[(v5,[(v6,[])])])]) = tree1 in v0+v1+v2+v3+v4+v5+v6
(i) What is the type-level problem with this code?
Answer
The initial accumulator is of type (Int,[t]). The folding function function must have type b -> a -> b which is (Int,[t])-> Int -> (Int,[t]). However, its return type is (a,[b]) which is (Int,[(Int,[t])).
(ii) Propose a recursive, non-polymorphic algebraic datatype Tree2 with constructor T2 such that the following code is correct:
(vi) What happens when you print makeTree2 and why?
Answer
It prints forever because of the lazy evaluation of the foldr and because each iteration applies f to the next value and the result of the fold on the rest of the list. So on every iteration, the type constructor and the integer value are known and will be printed; the content of the list is iteratively printed.
Question 3: Monads
3a)
Abstract
A monad consists of a type m a, the functions return and bind (also written >>=), and the optional >> operator. Given the following type:
data This a = This a
(i) Complete the implementation of return.
return = ...
Answer
return = This
(ii) Complete the implementation of >>=.
(>>=) (This x) f = ...
Answer
(>>=) (This x) f = f x
(iii) With the given implementation of >>=, and assuming the definition for f is:
f = This . id
What is the type of f?
f :: a -> ...
Answer
f :: a -> This a
3b)
Abstract
(b) A monad must also satisfy the three monad laws. Assume return and >>= are defined as above.
(i) Complete the expression so that the 1st monad law (left identity) holds:
\x -> (This x >>= return) ≡ ...
Answer
\x -> (This x >>= return) ≡ This x
(ii) Complete the expression so that the 2nd monad law (right identity) holds:
return x >>= ... ≡ This x
Answer
return x >>= This ≡ This x
(iii) Complete the expression so that the 3rd monad law (associativity) holds:
(This x >>= This) >>= This ≡ ... >>= (\x ... This x ... This)
Answer
(This x >>= This) >>= This ≡ This x >>= (\x -> This x >>= This)
3c)
Given following type:
data CouldBe a = Really a | Only a | None
(i) Complete the implementations of return and >>= so that the three monad laws are satisfied.
return = ...None >>= f = ...(Really ...) >>= f = ... x(... x) >>= f ... f x
This is a drag and drop question with possible answers from [CouldBe, Really, Only, None, f, x, a, =, >>= ]
Answer
return = ReallyNone >>= f = None(Really x) >>= f = f x(Only x) >>= f = f x
(ii) Explain your reasoning
Answer
CouldBe is a straightforward generalisation of Maybe, which is a monad. The only difference is the definition of (>>=) which has an additional case.
3d)
Abstract
Given following code which defines a type UMaybe and makes it an instance of the Monad typeclass with the given definitions of return and >>=. We define a function uLookup which takes a string and returns values of type UMaybe. In the function uCheck we use do-notation to combine calls to this function.
data UMaybe a = UJust a | UUnknown | UNothing deriving Showinstance Monad UMaybe where return = UJust (>>=) UNothing f = UNothing (>>=) UUnknown f = UUnknown (>>=) (UJust x) f = f xuLookup :: String -> UMaybe IntuLookup "1" = UJust 2uLookup "2" = UUnknownuLookup "3" = UJust 4uLookup "4" = UJust 5uLookup _ = UNothinguCheck v0 = do v1 <- uLookup v0 v2 <- uLookup (show v1) v3 <- uLookup (show v2) return (v1,v2,v3)main :: IO ()main = print $ uCheck "1"
(i) What is the value of v1?
Answer
v1 = 2
(ii) What is the return value of uLookup (show v1)?
Answer
uLookup (show v1) = UUnknown
(iii) What is the return value of uCheck "1"?
Answer
`uCheck “1” = UUnknown¦
(iv) What is the type of uCheck?
Answer
uCheck :: String -> UMaybe (Int,Int,Int)
(v) Explain your answers to (i) .. (iv)
Answer
Because v0 is “1”, uLookup returns 2, so v1 = 2.
uLookup (show v1) = uLookup "2" = Unknown
Because of the definition of bind, UUnknown will be passed through to the end of the computation. So uCheck “1” returns UUnknown.
The final return desugars to UUnknown >>= \v3 -> return (v1,v2,v3) and so the final result is also UUnknown.
The return type of uCheck is UMaybe (Int,Int,Int): if all calls to uLookup would succeed, v1, v2 and v3 would be Int.
Question 1: Algebraic Data Types in Real World Entities
1a)
Question
Write Haskell code to define an algebraic data type called Tower. A Tower value has three components: two integer values to represent height and width and a single character value to depict the shape of the top of the tower.
Answer
-- record style declaration worth 3 marksdata Tower = Tower { height :: Int, width :: Int, top :: Char }-- simple ADT declaration only worth 2 marks-- (without implicit accessors)data Tower = Tower Int Int Char
Also accept different names for value constructor. Partial marks for partial solutions:
1 mark for data Tower on left
up to 2 marks for definition on right hand side
1b)
Question
(b) A smart constructor constrains the creation of values for user-defined types like Tower. Why might smart constructors be helpful? Give a sensible implementation of a smart constructor for Tower values that satisfies the type specification below: mkTower :: Int -> Int -> Char -> Maybe Tower
Answer
A smart constructor enforces constraints on component values supplied to a value constructor, preventing the creation of instances with erroneous, inconsistent or impossible values (1 mark). Give 0.5 marks for a partial answer or a sensible example.
mkTower height width top| height < 0 || width <=0 = Nothing| otherwise = Just $ Tower height width top
1 mark for a sensible check
1 mark for a Nothing
1 mark for a value constructor call, wrapped in Just
1c)
Question
Which Haskell type class is associated with the generation of String representations of values?
The Show typeclass
Answer
The Show typeclass (1 mark). Only award 0.5 marks if student says show (lower-case) since that is a function.
1d)
Question
Write Haskell code to make it possible for Tower values to be converted to ASCII-art style String values. Your code should include the appropriate type class instantiation and the relevant function definition. Some examples of ASCII-art String values that might represent Tower instances are given below. The description is on the left hand side and the ASCII-art is on the right hand side.
Tower with height 2, width 1, top '*' *
|
|
Tower with height 1, width 4, top '+' +
| |
Tower with height 4, width 3, top '^' ^
| |
| |
| |
| |
Answer
Give marks for correctness and style of Haskell, up to max of 5. Example solution below. Accept reasonable alternatives.
instance Show Tower where {- 1 mark -} show t = {- 1 mark -} let w = width t h = height t section = if w == 1 then "|" {- 1 mark -} else "|" ++ (replicate (w-2) ' ') ++ "|" base = concat (replicate h (section ++ "\n")) {- ^^^ 1 mark -} top_space = (replicate ((w-1) 'div' 2) ' ') centred_top = top_space ++ [top t] ++ top_space ++ "\n" in {- 1 mark -} centred_top ++ base
1e)
Question
Now consider a single variant algebraic datatype called LeaningTower, where a LeaningTower value has two components: a Tower and an integer. This represents a tower that is leaning over at an integer angle (in degrees) to the vertical. In some sense, LeaningTower is a special case of Tower. Thinking about the definition for LeaningTower, and based on your experience, comment briefly on facilities for inheritance and code reuse in Haskell.
Answer
(Discussion, open-ended reflection. Not covered in lecture notes. 1 mark for each sensible point, up to a max of 3 marks.)
Composition of datatypes is the way to achieve inheritance. Inheritance is only possible in type classes (cf. interfaces in Java) - so we can inherit APIs but not fields or behaviors.
Code reuse in Haskell is less straightforward than in OO languages - need to access Tower fields from LeaningTower - function composition required. Also tight coupling.
Typeclasses might ameliorate this problem somewhat.
1f)
What is property-based testing?
Answer
QuickCheck tool does property-based testing. Express properties as functional assertions (predicates) and test these functions on arbitrary (randomly generated) input values of appropriate types. (1 mark)
1g)
Suppose you are given a function with the following type: willFallOver :: LeaningTower -> Bool. You know that towers with zero height or zero lean will never fall over. Construct a function propZero to express this property, such that propZero could be provided as an input to QuickCheck, assuming that the QuickCheck tool knows how to generate arbitrary LeaningTower values.
Answer
Example solution below. Award marks for alternatives.
propZero = \t -> if (height.tower) t == 0 || lean t == 0 then fallsOver t else True
1 mark for single-parameter predicate
0.5 marks for each ==0 check (x2)
0.5 marks for then fallsOver t
0.5 marks for else True
Question 2: Modeling a Gameshow
Abstract
This question is about modelling a gameshow, where a contestant needs to answer a sequence
of questions correctly to win a prize. The rules of the game are as follows:
• There is a maximum of 10 questions in the game
• If a contestant gets a sequence of 10 correct answers, they win £1024
• If a contestant gets a question wrong, the game ends immediately
• If the game ends because of an incorrect answer, then if the contestant has a sequence of
five or more correct answers before their incorrect answer, they win £32
• If the contestant does not have a sequence of five or more correct answers, then they do
not win any prize money
2a)
Question
Suppose the sequence of answers is modelled as a list of Bool values, with the most recent answer at the head of the list. A list element is True if the corresponding answer was correct, and False otherwise. Write Haskell code to calculate the prize for this contestant according to the game rules. Your function should have the following type: prize :: [Bool] -> Int
Answer
Up to 5 marks for translating the rules into appropriate Haskell code. Proportional marks for partially correct answer (e.g 2 for correct award of 1024, 2 for correct award of 32, 1 for correct award of 0).
Example solution below, accept reasonable alternatives.
prizeAwarded :: [Bool] -> IntprizeAwarded answers = if and answers && length answers == 10 then -- all 10 correct 2 ^ 10 else let lastfive = drop (length answers - 5) answers in if and lastfive && length lastfive == 5 then 2 ^ 5 -- five right in a row else 0 -- loser
prize :: [Bool] -> Intprize xs | fullCorrect = 2 ^ 10 -- all ten answers correct | consolation = 2 ^ 5 -- last five in a row correct | otherwise = 0 -- no prize where fullCorrect = length xs == 10 && all id xs consolation = length xs >= 5 && all id (drop (length xs - 5) xs)
2b)
Question
(b) The Haskell action askQuestion :: IO Bool selects a question from a database, asks the question to the contestant, receives the answer from the contestant and checks whether the answer is correct. Discuss why the type of the askQuestion code is appropriate.
Answer
This avoids any source code - the student can’t type anything in here - they need to use their imagination and reason carefully.
Bool for the result of the question - True if correct answer, False otherwise. (1 mark)
Wrapped in IO because of side-effecting operations - database read, then interaction with user - print / getLine / or alternatives… (2 marks)
2c)
Question
A single round of the quiz is modelled with the following function: quizRound :: [Bool] -> IO [Bool] where the parameter of the function is a list of question results before this round, and the result of the function is a list of question results after this round, wrapped in the IO monad. Write an implementation of the quizRound function. Your implementation should invoke askQuestion exactly once. You should use either a monadic bind or the do notation.
Answer
(1 mark for the list cons, 1 mark for calling nextQuestion, 2 marks for appropriate sequencing with do or bind).
Write Haskell code to model the full gameshow, with a top-level entity game :: IO Int where the integer value is the prize money won by the contestant. You should make use of the functions defined in earlier parts of this question; you may define further auxiliary functions, if required.
Answer
Give marks for correctness and style of Haskell, up to max of 5 marks. Example solution below.
quizRound :: [Bool] -> IO [Bool]quizRound answersSoFar = nextQuestion >>= (\x -> return (x:answersSoFar))quiz :: IO [Bool] -> IO Intquiz state = do answers <- state -- run this single round -- let state' = quizRound answers answers' <- quizRound answers -- check for end of game if (length answers' >= max_answers || not (head answers')) then -- game over return $ prizeAwarded answers' else do -- another round putStrLn "another round" quiz (pure answers')game :: IO Intgame = quiz (pure [])
2e)
Question
How might you tackle this modelling problem in a different way, if you were not constrained by the concrete types specified in this question?
Answer
talk about using state monad to capture quiz results from previous rounds (1 mark)
talk about need for IO, for user interaction (1 mark)
talk about using monad transformers to combine state and IO (1 mark)
Accept other reasonable solutions…
Question 3: Functional Abstractions
Abstract
Recall that the Functor typeclass is defined as follows:
class Functor f where
fmap :: (a → b) → f a → f b
3a)
Question
What is the purpose of the Functor typeclass?
Answer
The Functor typeclass allows us to apply a function to the contents of a type constructor (for example, a data structure such as a tree or a list, or an IO computation) (1 mark) in a structure-preserving way (1 mark).
3b)
Question
(b) Consider the ZeroOneMany data structure, defined as follows: data ZeroOneMany a = Zero | One a | Many [a]. Consider the following Functor instance for ZeroOneMany:
instance Functor ZeroOneMany where fmap f Zero = Many [] fmap f (One x) = Many [f x] fmap f (Many xs) = Many (map f xs)
Is this a sensible Functor instance? Justify your answer, referring to the functor laws.
Answer
This is not a sensible Functor instance (1 mark) since it violates the functor laws (1 mark). Although it typechecks and applies the function to the contents of the data structure, the fmap implementation is not structure-preserving (1 mark).
As an example, consider the functor law fmap id = id. If we were to apply fmap id (One 5), we would obtain Many [5] instead of One 5 (1 mark).
3c)
Question
Recall that the Monad typeclass is defined as follows:
class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b
Given that type constructor m is an instance of the Monad typeclass, define the function: fmapM :: Monad m => (a -> b) -> m a -> m b in terms of return and >>=.
Answer
fmapM f m = m >>= \x -> return (f x)
(1 mark for the left-hand-side of the equation fmapM f m or appropriate anonymous arguments, 1 mark for m >>= \x -> ..., 1 mark for return (f x)).
3d)
Abstract
The Bifunctor typeclass is defined as follows:
class Bifunctor p where bimap :: (a -> b) -> (c -> d) -> p a c -> p b d first :: (a -> b) -> p a c -> p b c second :: (b -> c) -> p a b -> p a c
Further, consider the BiBucketTree data type, defined as follows, which represents a tree which can either contain lists of values of type a or of type b:
data BiBucketTree a b = Leaf | LeftNode [a] (BiBucketTree a b) (BiBucketTree a b) | RightNode [b] (BiBucketTree a b) (BiBucketTree a b)
Question
(i) Based on the type signatures of bimap, first, and second, describe the purpose of the Bifunctor typeclass. Give an example of when you might wish to use a Bifunctor instance.
Answer
Given a type constructor with two type arguments (1 mark), the Bifunctor typeclass allows us to apply two functions to its contents in a structure-preserving way (1 mark).
An example could be applying a function to the Either a b type, where either a function is applied to the contents of the Left type constructor, or a function is applied to the contents of the Right type constructor (1 mark for an example; one could be the BiBucketTree above).
Question
(ii) Write a function reverseBuckets :: BiBucketTree a b -> BiBucketTree a b which reverses the contents of the list at each node. For example, reverseBuckets (LeftNode [1,2,3] Leaf Leaf) = LeftNode [3,2,1] Leaf Leaf
(3 marks for correct implementation; 2 for minor mistakes; 1 if there is an attempt at case splitting.)
Question
(iii) Write a Bifunctor instance for BiBucketTree.
Answer
There are a few possible solutions. The most compact is:
instance Bifunctor BiBucketTree where bimap f g Leaf = Leaf bimap f g (LeftNode xs bt1 bt2) = LeftNode (map f xs) (bimap f g bt1) (bimap f g bt2) bimap f g (RightNode xs bt1 bt2) = RightNode (map g xs) (bimap f g bt1) (bimap f g bt2) first f = bimap f id second g = bimap id g
Alternatively it is possible to implement bimap in terms of first and second:
instance Bifunctor BiBucketTree where first f Leaf = Leaf first f (LeftNode xs bt1 bt2) = LeftNode (map f xs) (first f bt1) (first f bt2) first f (RightNode xs bt1 bt2) = RightNode xs (first f bt1) (first f bt2) second f Leaf = Leaf second f (LeftNode xs bt1 bt2) = LeftNode xs (second f bt1) (second f bt2) second f (RightNode xs bt1 bt2) = RightNode (map f xs) (second f bt1) (second f bt2) bimap f g = first f . second g
It would also be acceptable to implement all functions explicitly.
1 mark for defining an instance with stubs for all methods.
1 mark for case-splitting within the function definitions.
1 mark for correct recursive calls on sub-structures.
1 mark for ensuring map f xs is applied to each bucket.
1 mark for correct implementations of all functions (either bimap in terms of left, right; or left, right in terms of bimap; or all explicitly)
(a) Give the type of f1, explain what the type means, and give the value of test_f1, where the definitions are:
f1 k xs = [x-k | x <- xs, x>k]test_f1 = f1 2 [3, 4, -3, 1, 2, 7]
Answer:
Type: f1 :: (Num a, Ord a) => a -> [a] -> [a]
The type means that the function takes two arguments: a value of type a, and a list whose elements have type a; a can be any type provided that it is in the Num class and in the Ord class. The function returns a list of the same type.
Value of test_f1: [1, 2, 5]
(b) Give the type of f2 and the value of test_f2:
f2 f g h = f . g . htest_f2 = f2 (*5) (+1) (*2) 3
Answer:
Type: f2 :: (c->d) -> (b->c) -> (a->b) -> a -> d
Value of test_f2: 35
(c) Give the type of f3 and show how you infer the type.
f3 (a,b) [c,d,e] = (a+b, c+d<e)
Answer:
Type: f3 :: (Num a, Num b, Ord b) => (a,a) -> [b] -> (a, Bool)
Variables a, b must have the same type a which must be in Num because (+) :: Num a ⇒ a → a → a. Variables c, d, e must have the same type b which must be in Ord because (<) :: Ord a ⇒ a → a → Bool. Hence the argument type must be (a,a) and [b] and the result type must be (a,Bool).
(d) Give the type and definition of the map function.
Answer:
map :: (a->b) -> [a] -> [b]map f [] = []map f (x:xs) = f x : map f xs
(e) Suppose a Haskell program contains the following definitions:
n = 3 + nxs = [if x==3 then n else x | x <- [0..5]]k = length xs
Give the values of n, xs, and k. For any undefined value, write bot (for bottom). If k is bot, explain why; if it is not, explain how its value is computed.
Answer:
The value of n is bottom (undefined) because the addition requires the value of n, and this cannot be computed before the addition is performed. Thus n has a circular definition that cannot be computed.
The list xs has the value [0, 1, 2, bot, 4, 5]; thus the list itself is not bottom, but one of its elements is bottom.
The value of k is 6, which is defined because the length function only examines the list structure and does not need to use any of the values in the list.
with value [1, 2, 3, …] which is infinitely long. (i) Write a definition of posInts.
Answer:
posInts :: [Int]posInts = let f x = x : f (x+1) in f 1
(ii) Explain how lazy evaluation makes it possible to define infinite data structures like posInts.
Answer:
Without lazy evaluation, the expression f 1 would never return a value; it would go into an infinite loop filling up memory with the list. Lazy evaluation causes a closure to be returned, which is a representation of how to compute more of the result when needed. Only as much of the list will be constructed as actually needed by the program.
Question 2: Embedded Domain Specific Languages
(a) Define the term embedded domain specific language. Give three examples of Haskell features that are useful for implementing EDSLs, and explain why they are useful.
Answer:
A DSL is a language designed specifically for an application area (the “domain”), and its constructs and notation are aimed at that application area, not for general purpose programming. An EDSL is a DSL that is implemented by embedding it in a general purpose language, such as Haskell; this means that the existing Haskell tools can be used to compile the EDSL. Thus an EDSL is based on a library rather than a compiler. To make this possible, the host language needs to have flexible syntax and semantics.
Haskell’s algebraic data types enable a library to define data structures that are suitable for an application domain. Type classes enable the semantics of operations like showing, reading, comparison, etc. to be adjusted to suit the needs of the EDSL. Monads provide the ability to define new control structures as needed. Templates allow code to be generated from new syntactic constructs. Operator definitions allow expressions to be targeted to the application area.
(b) Consider a type Colour defined as follows:
data Colour = Red | Green | Blue
(i) Define a function hot :: Colour → Bool, so that hot returns True if its argument is Red, and returns False if its argument is not Red.
Answer:
hot :: Colour -> Boolhot Red = Truehot Green = Falsehot Blue = False
(ii) Give the necessary declarations so that Colour is a member of the Show class, and the result of showing Red, Green, or Blue is "R", "G", or "B" respectively.
Answer:
instance Show Colour where show Red = "R" show Green = "G" show Blue = "B"
(c) Write a parser vname :: Parse String that matches an access to a variable name that must begin with a letter, may contain any number of additional letters, followed by any number of digits. However, a letter is not allowed after a digit. Thus "ab345", "x", and "znv6" are all valid, but "34", "ab37q", and "*x7" are not valid.
Answer:
var :: Parse Stringvar = do x <- letter xs <- many letter ys = many digit return (x : (xs++ys))
(d) Consider the following definition:
data Tree = Tip Int | Node Int Tree Tree
(i) A binary search tree is a Tree such that all the numbers in the left subtree of a node are less than the Int in that node, and all the numbers in the right subtree are greater than the Int in that node. Write a constant bst1 :: Tree which is a binary search tree, and write a constant notbst1 :: Tree which is not a binary search tree. Both values should contain at least two Nodes.
(ii) Write a function searchBst :: Int → Tree → Bool, such that searchBst n t returns True if n appears in t, and False otherwise, provided that t is a binary search tree. In the worst case, the function should take time proportional to the depth of the tree. If the argument t is not a binary search tree, then the result is not required to be correct.
Answer:
searchBst n (Tip x) = n==xsearchBst n (Node x a b) = n==x || searchBst n (if n<x then a else b)
(iii) Write a function search :: Int → Tree → Bool, such that search n t returns True if n appears in t, and False otherwise. The argument t does not need to be a binary search tree. Estimate the worst case time required by search.
Answer:
search n (Tip x) = n==xsearch n (Node x a b) = n==x || search n a || search n b
The worst case time for search is proportional to the number of elements in the tree.
(e) Simplify the following lambda expression. Show each step in the reduction, and for each step show which conversion rule you are using.
(a) Consider the following definition of func. The types for MyMaybe, myHead, and myTail are given below.
func :: [a] -> MyMaybe afunc xs = do a <- myTail xs b <- myTail a c <- myHead b return cdata MyMaybe a = MyNothing | MyJust amyHead :: [a] -> Maybe amyTail :: [a] -> Maybe [a]
Give the types of the monadic functions >>= and return, and explain what they do. Translate func into a functional definition that uses the monadic functions return and >>=, and that does not use the “do” syntax.
Answer:
class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a
= takes a monadic action that yields a result of type a, and a function that takes a value of type a and returns a new monadic action that yields a result of type b. It runs the first action, passes the result to the second action, runs it, and returns the results.
return takes a value of type a, and returns a monadic action that, when performed, will yield that value. Thus it takes a value and puts it into the monad.
func xs = myTail xs >>= (\a -> myTail a >>= (\b -> myHead b >>= (\c -> return c)))
(b) Assume that the list xs has a finite (but unbounded) number of elements. Prove that sum (map (2*) xs) = 2 * sum xs. Use the following definition of sum:
sum :: Num a => [a] -> asum [] = 0sum (x:xs) = x + sum xs
Answer:
Proof by induction over xs.
Base case []. sum (map (2*) []) = sum [] = 0 = 2*0 = 2 * sum [].
Induction case, using statement of theorem as hypothesis.
sum (map (2*) (x:xs)) = sum (2*x : map (2*) xs) = 2*x + sum (map (2*) xs)
= 2*x + 2 * sum xs = 2 * (x + sum xs) = 2 * sum (x:xs)
(c) Suppose a Haskell program contains the expression f exp1 exp2. Explain why the result is guaranteed to be correct even if the arguments are evaluated in parallel. Explain why, in general, the arguments to procedures in an imperative language cannot be evaluated in parallel.
Answer:
A pure functional language, such as Haskell, satisfies the Church Rosser property, because it has no side effects (i.e. the language is, at its core level, the lambda calculus). This means that any order of evaluation will yield the same result, as long as that order achieves a result. In particular, this means that if an expression has several redexes, they can be evaluated in any order, including parallel order, without affecting the result. Consequently, the language implementation is free to use parallelism without running the risk of producing incorrect results.
In an imperative language, the expressions may be calls to procedures that make assignments to global variables, and the result may depend on the order of assignments. For this reason, a strictly defined order (e.g. left to right) is needed to obtain a uniquely defined result, and parallel evaluation may give a different answer than sequential evaluation.
(d) Define the term referential transparency, and explain why Haskell has referential transparency even if mutable IORef variables are used.
Answer:
Referential transparency means that if e1=e2, then for all exp, exp[e1/e2] = exp (provided that the expressions are all well typed). In other words, referential transparency means you can “substitute equals for equals” without changing the meaning of a program.
Imperative languages do not have equations, so they lack referential transparency. (Alternatively, if one thinks that an assignment statement in a language like C or Java is an equation because it looks like one, you can prove that 0=1 by simple algebra; imperative languages are not referentially transparent.)
The reason that Haskell is referentially transparent even if you have IORefs in an IO monad is that the terms denote computations of type IO a, not ground values of type a. Thus equational reasoning can be used to prove that two computations are equivalent, but not to obtain contradictions like 0=1.
Question 4: Interactive Programming
(a) Write an interactive program called prog. It promts the user to enter a number (of type Double). If the user enters the character 'q' the program prints "Bye" and stops. If the user enters a number, the program prints the count of numbers so far, and their mean, and then prompts the user for more input. The program does not need to handle incorrect input data. For example:
*Main> prog
Enter a number: 3
Count is 1, mean is 3.0
Enter a number: 6
Count is 2, mean is 4.5
Enter a number: q
Bye
Answer:
prog :: IO ()prog = helper 0 []helper :: Int -> [Double] -> IO ()helper count xs = do putStr "Enter a number: " xs <- getLine if head xs == 'q' then do putStrLn "Bye" return () else do let x = read xs :: Double let mean = sum (x:xs) / (count+1) putStrLn ("Count is " ++ show (count+1) ++ ", mean is " ++ show mean) prog (count+1) (x:xs)
(b) Consider the head and tail operations on lists. (i) Define the term partial function. Explain why head and tail are partial.
Answer:
A partial function is undefined on some elements of its domain. Head and tail are partial, because they are undefined when applied to the empty list.
(iii) The function f xs = head (tail xs) is partial. Define a total function safe_f, using the Maybe monad, which is similar to f. If f xs is defined, then safe_f xs = Just (f xs); if f xs is undefined, then safe_f xs = Nothing.
(iii) Discuss the tradeoffs between using the Maybe monad for computations that might fail, and using an exception handler.
Answer:
The Maybe approach guarantees that every operation is checked, and it identifies exactly where a computation fails. Thus it gives a precise determination of the location of a failure. However, it requires the code to be expressed using Maybe types and in the monadic style, and checking every operation requires some computation time. The exception handler does not identify precisely where a computation failed, but it does not require the code to be written in a special style.
(c) Suppose a program with a graphical user interface has a global state that needs to be updated when an event occurs. The program has a number of buttons; when a button is pressed, a computation takes place which may update the global state. State whether an MVar or an IORef would be more appropriate for maintaining the global state. Justify your answer.
Answer:
An IORef is a mutable variable whose access is controlled by the IO monad. An IORef must be created with an initial value; reading an IORef does not modify it. If IORefs are used in a concurrent or parallel program, it is possible for there to be a race condition, resulting in undefined semantics.
An MVar is a mutable variable that can be either full or empty, and is intended for use in concurrent programs. If a thread attempts to read an MVar, it is blocked until the MVar becomes full. If a thread takes an MVar, the MVar becomes empty and the thread continues execution with the value that was in it. When a thread writes to an empty MVar, it becomes full and if a thread was blocked reading it, that thread is awakened. MVars provide mutable state with locking, so multiple threads can share state safely, without allowing race conditions.
(a) Give the type of f1, explain what the type means, and give the value of test_f1, where the definitions are:
f1 g xs = map g ((map (2*) xs)test_f1 = f1 (7+)[3, 4, -3, 1, 2, 7]
Answer:
f1 :: (Num a) => (a->a) -> [a] -> [a]
test_f1 = [13, 15, 1, 9, 11, 21]
The type means that the function takes two arguments: a function that takes a value of type a and returns another value of the same type, and a list whose elements have type a; a can be any type provided that it is in the Num class. The function returns a list of the same type. [1 mark for the type, 1 mark for the value.]
(b) What is the type of 2 in general? In the expression 2 * 3.14, assume that 3.14 :: Double. What is type type of 2 in this expression? Is it converted to Double at runtime?
Answer: 2 :: Num a ⇒ a which means that it is a polymorphic constant that can have any numeric type. In the expression given, 2 :: Double because the two operands to * must have the same type. There is no cast or runtime conversion. [Straightforward; requires knowing about numeric type classes.]
(c) Give the type of f3 and show how you infer the type.
f3 (a,b) [c,d,e] = if a<b then c+d else d-e
Answer:
f3 :: (Ord a, Num b) => (a,a) -> [b] -> b
Variables a, b must have the same type a which must be in Ord because (<) :: Ord a ⇒ a → a → a. Variables c, d, e must have the same type b which must be in Num because (+) :: Num a ⇒ a → a → a.
[Requires understanding of type inference and type classes.] [1 mark for the function type, 1 mark for the class constraints, marks for the explanation of the inference.]
(d) Give the type and definition of the foldr function.
Answer:
foldr :: (a->b->b) -> b -> [a] -> bfoldr f a [] = afoldr f a (x:xs) = f x (foldr f a xs)
[Bookwork.] [1 mark for type, 1 mark for definition.]
(e) Explain the difference between an equation in Haskell and an assignment statement in an imperative language. Discuss how Haskell programs can perform computations, even though they cannot modify the values of variables.
Answer: An assignment is a command to evaluate the right hand side and then to store the result into the left hand side, discarding the old value. An equation is an assertion that the left and right hand sides have the same value. An equation can be used to define a variable, but not to change it. Haskell programs operate by calculating new values and binding them to new names (or commonly, to the same name in a different scope corresponding to an iteration of a recursive function).
[Requires understanding. 2 marks for difference between the two; 2 marks for how computation is performed.]
with value [1, 2, 1, 2, …] which is infinitely long. (i) Write a definition of onetwo. (ii) Give the value of (take 5 (map (3*) onetwo)), and explain how it can be evaluated even though onetwo is an infinite list.
Answer: (i)
onetwo :: [Int]onetwo = 1 : 2 : onetwo
(ii) [3, 6, 3, 6, 3] This is possible because of lazy evaluation. Onetwo requires only two cells to represent, with a circular pointer. The map defines an infinite list, but only the part that is needed is computed, and take needs only 5 elements. [Requires understanding lazy evaluation.]
Question 2: Domain Specific Languages and Parsec
(a) Define the term embedded domain specific language. Give three examples of Haskell features that are useful for implementing EDSLs, and explain why they are useful.
Answer: A DSL is a language designed specifically for an application area (the “domain”), and its constructs and notation are aimed at that application area, not for general purpose programming. An EDSL is a DSL that is implemented by embedding it in a general purpose language, such as Haskell; this means that the existing Haskell tools can be used to compile the EDSL. Thus an EDSL is based on a library rather than a compiler. To make this possible, the host language needs to have flexible syntax and semantics. Haskell’s algebraic data types enable a library to define data structures that are suitable for an application domain. Type classes enable the semantics of operations like showing, reading, comparison, etc. to be adjusted to suit the needs of the EDSL. Monads provide the ability to define new control structures as needed. Templates allow code to be generated from new syntactic constructs. Operator definitions allow expressions to be targeted to the application area.
[The term is bookwork. A few examples have been discussed in lectures, but many more are apparent with some thought.] [1 mark for definition, 1 mark for each feature explanation.]
(b) Explain the basic concept behind the Parsec parser combinatory library, and give a four examples of different combinators and their usage.
Answer: Parsec is monadic parser combinatory library. With Parsec, parsers are constructed by combining higher-order combinators to create larger expressions. Combinators are functions without free variables, the way they are used in Parsec’s monad they take no explicit arguments. Examples are many1, string, parens.
[Requires knowing the basics of Parsec (1) + 1 mark for every example]
(c) How is a choice of parsers expressed in Parsec? What role does the "try" combinatory play? Provide a complete string parsing example that uses both.
Answer: Choice is expressed using the <|> combinatory. Normally if a parse of an option is performed and partially succeeds, it consumes the parsed input. The “try” combinator allows to parse without consuming the input if the parse fails.
[This is similar to examples that have been discussed in lectures.] [2 marks for explaining choice and try; 1 mark for use of Parse monad, 2 marks for accepting the specified syntax.]
(d) Write a Parsec-based parser to parse a sentence into a list of lowercase words. The resulting list should only contain the words, no punctuation characters.
Answer:
getWords :: Parser [String]getWords = do words <- sepBy1 word space return map lcNoPunct wordsword = many1 letterlcNoPunct word = filter (\c -> not (c `elem` ['.','?','!',';',',','—']) ) (map toLower word)
[Straightforward use of Parsec combinators, map and filter. 2 marks for each, meaning and syntax]
Question 3: Monads and IO
(a) Consider a function f which computes the value of (x*y) / z. However, in order to avoid the possibility of an exception (overflow or division by 0), f uses safeMult and safeDiv to perform the calculations. Their types are given below. Write two definitions of f :: Double → Double → Double → Maybe Double using the Maybe monad: a definition using a do expression, and an alternative definition using the >>= operator.
myMult, myDiv :: Double -> Double -> Maybe Double
Answer:
func :: a -> a -> a -> Maybe afunc x y z = do a <- safeMult x y b <- safeDiv a z return cfunc x y z = safeMult x y >>= (\a -> safeDiv a z >>= (\b -> return b))
[Requires knowing the monadic operations (2 marks for types, 2 marks for explanation), and the desugaring rules for do (2 marks).]
(b) Assume that the list xs has a finite (but unbounded) number of elements. Prove that map f (map g xs) = map (f . g) xs
Answer: Proof by induction over xs.
Base case [].
map f (map g []) = map f [] = [] = map (f . g) []
Induction case, using statement of theorem as hypothesis.
map f (map g (x:xs) = map f (g x : map g xs) = f (g x) : map f (map g xs) = (f . g) x : map (f . g) xs = map (f . g) (x : xs)
[Requires understanding and ability to carry out equational reasoning.] [1 mark for base case, 2 for setup, 1 for use of hypothesis, 2 for accuracy and completeness.]
(c) Explain why equational reasoning can be used to prove theorems about programs in Haskell, it cannot be used to prove theorems about programs in an imperative language.
Answer: Equational reasoning requires that equations denote pure equality, so that if exp1 = exp2 then the two expressions are interchangeable. This property is called referential transparency, and Haskell satisfies it. Imperative languages do not have this property because variables are mutable, so an equation may be true at one point during the execution of a program but untrue at another point. [Bookwork.]
(d) Explain how IORef variables are used in a Haskell program with a graphical user interface implemented with gtk. Explain why the Haskell program satisfies referential transparency even though it contains IORef variables.
Answer: The program needs to have a global state, which is kept in an IORef. It is impossible to hold the state in an argument to a recursive function, or as a monadic state (the two most common ways to represent state) because the order of events depends on the order in which the user clicks on GUI controls, and this is outside the control of the program. However, when an event is triggered, that event performs a monadic computation with referential transparency. Thus the program executes as a collection of separate computations, which are triggered by the outside world. As the program initializes the GUI, it performs a sequence of monadic operations, again with referential transparency. Furthermore, even though the IORef variable is mutable, it remains the case that any two expressions that are equal are interchangeable. Referential transparency does not preclude mutable variables or I/O side effects. [This topic is discussed in depth in lectures. This question is bookwork, but it takes significant understanding to be able to give the answer.]
Question 4: Imperative Style in Haskell
(a) Why does the following code not work in Haskell? (Assume that rand is a function returning a pseudo-random number between 1 and 10.)
let a=0 bs=[] bs = map rand [1..10] a = a + bs !! 0 a = a * bs !! 1 bs !! 2 = bs !! 0 + bs !! 1 a = a - b !! 2in a
Answer: This code does not work because (i) variables in Haskell are immutable (ii) the lhs of an assignment can’t be an expression (iii) lists are also immutable
[This requires understanding the concept of immutable variables and the meaning of the assignment symbol in Haskell.]
(b) (i) Explain two mechanisms for handling state in Haskell and explain their use and differences. (ii) Use one one of them to create equivalent imperative-style code for a) that will work correctly
Answer: (i) For example:
An IORef is a mutable variable whose access is controlled by the IO monad. An IORef must be created with an initial value; reading an IORef does not modify it. If IORefs are used in a concurrent or parallel program, it is possible for there to be a race condition, resulting in undefined semantics.
An MVar is a mutable variable that can be either full or empty, and is intended for use in concurrent programs. If a thread attempts to read an MVar, it is blocked until the MVar becomes full. If a thread takes an MVar, the MVar becomes empty and the thread continues execution with the value that was in it. When a thread writes to an empty MVar, it becomes full and if a thread was blocked reading it, that thread is awakened. MVars provide mutable state with locking, so multiple threads can share state safely, without allowing race conditions.
Question 2: Embedded Domain Specific Languages (EDSLs)
2a)
Define the term embedded domain specific language. Give three examples of Haskell features that are useful for implementing EDSLs, and explain why they are useful.
Answer: A DSL is a language designed specifically for an application area (the “domain”), and its constructs and notation are aimed at that application area, not for general purpose programming. An EDSL is a DSL that is implemented by embedding it in a general purpose language, such as Haskell; this means that the existing Haskell tools can be used to compile the EDSL. Thus an EDSL is based on a library rather than a compiler.
Haskell’s algebraic data types enable a library to define data structures that are suitable for an application domain. Type classes enable the semantics of operations like showing, reading, comparison, etc. to be adjusted to suit the needs of the EDSL. Monads provide the ability to define new control structures as needed. Templates allow code to be generated from new syntactic constructs. Operator definitions allow expressions to be targeted to the application area.
2b)
Consider a type Colour defined as follows:
data Colour = Red | Green | Blue
(i) Define a function hot :: Colour → Bool, so that hot returns True if its argument is Red, and returns False if its argument is not Red.
Answer:
hot :: Colour -> Boolhot Red = Truehot Green = Falsehot Blue = False
(ii) Give the necessary declarations so that Colour is a member of the Show class, and the result of showing Red, Green, or Blue is "R", "G", or "B" respectively.
Answer:
instance Show Colour where show Red = "R" show Green = "G" show Blue = "B"
2c)
Write a parser vname :: Parse String that matches an access to a variable name that must begin with a letter, may contain any number of additional letters, followed by any number of digits. However, a letter is not allowed after a digit. Thus "ab345", "x", and "znv6" are all valid, but "34", "ab37q", and "*x7" are not valid.
Answer:
var :: Parse Stringvar = do x <- letter xs <- many letter ys = many digit return (x : (xs++ys))
2d)
Consider the following definition:
data Tree = Tip Int | Node Int Tree Tree
(i) A binary search tree is a Tree such that all the numbers in the left subtree of a node are less than the Int in that node, and all the numbers in the right subtree are greater than the Int in that node. Write a constant bst1 :: Tree which is a binary search tree, and write a constant notbst1 :: Tree which is not a binary search tree. Both values should contain at least two Nodes.
(ii) Write a function searchBst :: Int → Tree → Bool, such that searchBst n t returns True if n appears in t, and False otherwise, provided that t is a binary search tree. In the worst case, the function should take time proportional to the depth of the tree. If the argument t is not a binary search tree, then the result is not required to be correct.
Answer:
searchBst n (Tip x) = n==xsearchBst n (Node x a b) = n==x || searchBst n (if n<x then a else b)
(iii) Write a function search :: Int → Tree → Bool, such that search n t returns True if n appears in t, and False otherwise. The argument t does not need to be a binary search tree. Estimate the worst case time required by search.
Answer:
search n (Tip x) = n==xsearch n (Node x a b) = n==x || search n a || search n b
The worst case time for search is proportional to the number of elements in the tree.
2e)
Simplify the following lambda expression. Show each step in the reduction, and for each step show which conversion rule you are using.
Consider the following definition of func. The types for MyMaybe, myHead, and myTail are given below.
func :: [a] -> MyMaybe afunc xs = do a <- myTail xs b <- myTail a c <- myHead b return cdata MyMaybe a = MyNothing | MyJust amyHead :: [a] -> Maybe amyTail :: [a] -> Maybe [a]
Give the types of the monadic functions >>= and return, and explain what they do. Translate func into a functional definition that uses the monadic functions return and >>=, and that does not use the “do” syntax.
Answer:
class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a
>>= takes a monadic action that yields a result of type a, and a function that takes a value of type a and returns a new monadic action that yields a result of type b. It runs the first action, passes the result to the second action, runs it, and returns the results.
return takes a value of type a, and returns a monadic action that, when performed, will yield that value. Thus it takes a value and puts it into the monad.
func xs = myTail xs >>= (\a -> myTail a >>= (\b -> myHead b >>= (\c -> return c)))
3b)
Assume that the list xs has a finite (but unbounded) number of elements. Prove that sum (map (2*) xs) = 2 * sum xs. Use the following definition of sum:
sum :: Num a => [a] -> asum [] = 0sum (x:xs) = x + sum xs
Answer: Proof by induction over xs.
Base case []. sum (map (2*) []) = sum [] = 0 = 2*0 = 2 * sum [].
Induction case, using statement of theorem as hypothesis.
sum (map (2*) (x:xs)) = sum (2*x : map (2*) xs) = 2*x + sum (map (2*) xs)
= 2*x + 2 * sum xs = 2 * (x + sum xs) = 2 * sum (x:xs)
3c)
Suppose a Haskell program contains the expression f exp1 exp2. Explain why the result is guaranteed to be correct even if the