Declarative Programming I Functional Programming in Haskell Unassessed Exercises Tony Field (Room 376) Answer as many as you can at your own speed. These exercises do NOT need to be submitted for marking. Model answers to each set will be handed out in due course. Functional Programming in Haskell Unassessed Exercise Sheet 1: Expressions, Built-in Types and Prelude Introduction All the problems on this sheet can be solved by typing expressions at the Hugs prompt: Prelude> type your expression here and press RETURN First read the laboratory notes provided, which introduces you to the Department’s computers and tells you how to start the Haskell (Hugs) system. As you do each question, read any accompanying notes carefully. Also, most importantly, make up your own problems and try them out too. Note that an important objective of this first batch of problems is to introduce you to some of Haskell's built-in types and functions (Haskell's so-called prelude). Within Hugs, you can find out more about any known identifier, including types, functions, classes etc., by typing :info identifier (or use :i for short), e.g. :info +, :i Int etc. To get a list of all function names that are in scope at any time type :names (or :n). You will probably not be able to complete these exercises in the two lab sessions allocated before Thursday's lectures, but don't panic! There are more questions here than you need, so skip over some of them once you've got the hang of it. 1. Type in and evaluate the following Haskell expressions: (a) (b) 2 - 3 - 4 2 - ( 3 - 4 ) Q: What does this tell you about the associativity of the Haskell ‘-’ operator? Remark: a left-associative operator op implies that successive applications are bracketed from the left, so that a op b op c is evaluated as ( ( a op b ) op c ). For a rightassociative operator, it would be evaluated as ( a op ( b op c ) ). (c) (d) (e) 100 `div` 4 `div` 5 100 `div` ( 4 `div` 5 ) 2 ^ 3 ^ 2 Remark: div is the prefix integer division function (quotient function), as in div 12 5 which gives the answer 2. The back quotes in `div` turns the prefix function into an infix operator, as in 12 `div` 5 (try this out). Do not confuse back quotes with the forward quotes that are used to delimit characters. Note also that an infix operator can be turned into a prefix function by enclosing it in brackets. For example, the expression (+) 8 5 means the same as 8 + 5. Q: What can you say about the associativity of the `div` and ^ operators? (f) (g) (h) 3 - 5 * 4 2 ^ 2 * 3 2 * 2 ^ 3 Q: What can you say about the relative precedence of the -, * and ^ operators? Remark: An operator’s precedence is an integer in the range 0-9 which is used to determine the order in which multiple operator applications are evaluated. If op1 has a higher precedence (a higher number) than op2 then a op1 b op2 c evaluates to ( ( a op1 b ) op2 c ) and a op2 b op1 c evaluates to ( a op2 ( b op1 c ) ). We say that op1 binds more tightly than op2, as with * and + respectively, for example. Experiment with the other Haskell operators. (i) (j) (k) (l) (m) (n) ( 3 ord chr chr 'p' 'h' + 4 - 5 ) == ( 3 - 5 + 4 ) 'a' ( ord 'b' - 1 ) ( ord 'X' + ( ord 'a' - ord 'A' ) ) /= 'p' <= 'u' Remark: The alphabetic characters ‘a’, ‘A’, ‘b’, ‘B’ etc. are encoded by computers as numbers. The most commonly used numbering scheme is called the ASCII encoding. The ASCII code for a character c can be obtained by the Haskell function ord (for ordinal value). The inverse of ord is called chr. See the attached ASCII table and check it out by applying the ord function to various characters. The operators ==, /=, <, >, <=, >= also work on characters using the ASCII numeric representation in the obvious way, e.g. 'a' < 'b' gives True because ord 'a' < ord 'b'. (o) (p) sqrt 2 ^ 2 sqrt 2 ^ 2 – 2 (try also sqrt 2 ^ 2 == 2 ) (sqrt is a function that calculates the square root of a given number). Q: What does this tell you about the relative precedence of prefix function application and infix function application? Remark: (o) gives a rather unintuitive answer. The reason is that floating-point arithmetic (arithmetic with Floats and Doubles) is only an approximation to arithmetic with real numbers. Computers use a finite representation for what is, mathematically, an infinite and continuous object (the infinite real line). The associated arithmetic is therefore subject to artefacts such as rounding error (we don’t get exactly the right answer) and overflow (the answer is too big or too small for the computer to represent). To understand this fully you will have to wait until the lectures on computer arithmetic which will follow shortly in another course. Finally, for practice, work out how Haskell brackets the following expressions and test your answers by typing in equivalent expressions with the brackets explicitly inserted. Note: the function truncate is a prefix function which rounds a floating point number (Float) down to the nearest Int, e.g. truncate 19.567 = 19; max and min respectively return the larger and smaller of two given numbers (works with Int, Integer, Float and Double types). (q) (r) (s) (t) not 1 + cos 2 ^ True || 5 < 6 sin 0.7 ^ 2 2 * pi ^ 2 >= truncate pi ^ `mod` truncate 2.8 ^ 2 sqrt 5 * 4 4 || sin pi < max 0.5 0.9 2 < 0.8 && pi > sin 0.8 2. Using the Haskell operators div, mod and ord, where necessary, write expressions to find the values of the following: (a) (b) (c) (d) (e) The last digit of the number 123 The penultimate digit of the number 456 The eighth lower-case letter of the alphabet (i.e. starting from ‘a’) The “middle” upper-case letter of the alphabet The minimum number of 13 gallon barrels needed to hold the entire contents of an 823 gallon vat 3. Using the minimum number of brackets as possible, write down expressions which evaluate to True iff: (a) (b) (c) (d) (e) (f) (g) (h) (i) (j) 100 is exactly divisible by 10 22 is even (do not use the built-in even function in Haskell!) The penultimate digit of 139 is different from the last digit of 55 The letter ‘z’ is the 26th lower-case letter of the alphabet The letter ‘c’ is either the 1st or the 3rd lower-case letter of the alphabet The letter ‘b’ is neither the 1st nor the 3rd lower-case letter of the alphabet As for (e) but this time do not use the not operation 100 lies between 50 and 200 10 does not lie between 50 and 200 As for (h) but do not use the not operation 4. All of the following expressions are syntactically correct, but some of them have some form of type error. For each, work out whether the expression is correctly typed or not. Where the type is incorrect work out how Haskell has implicitly bracketed the expressions. (a) (b) (c) (d) (e) (f) 2 * 3 == 6 6 == 2 * 3 2 + 3 == 5 not 2 * 3 == 10 3 == 4 == True if True then False else True == True (g) (h) (i) if 3 == 4 then ord 'a' else ord 'b' ord if 3 == 4 then 'a' else 'b' + 1 8 > 2 ^ if 3 == 4 then 2 else 3 == False Assuming that the expressions with incorrect types were a result of the programmer accidentally omitting parentheses, suggest where they might be added to make the types of the expressions correct. What types do these expressions now have? 5. Write a qualified expression using a let which finds the two roots of the equation ax2 + bx + c = 0 using the formula b (b2 4ac) ______________ 2a in the case where a=1, b=4 and c=1.5. Write your expression in such a way that the term inside the square-root is computed only once and return the two roots in the form of a tuple. 6. Using `div` and `mod` write an expression which converts time (an integer called s, say), representing the number of seconds since midnight, into a triple of integers representing the number of (whole) hours, minutes and seconds since midnight. Use a let to perform the calculation for the specific case where s=8473. 7. A polar coordinate (r,) can be converted into cartesian coordinates using the fact that the x-axis displacement is r cos , and the y-axis displacement is r sin Write a let expression which converts the polar coordinate (1,/4) into cartesian coordinates represented by a pair of Floats. Remark: Haskell allows you to match tuples in qualified expressions. As an example, in the same way that we can write let x = 5 in blah, which gives x the value 5 in expression blah we can also write let (x,y) = (5,6) in blah which simultaneously gives x the value 5 and y the value 6. Alternatively, there are built-in functions called fst and snd which will respectively deliver the first and second elements of a given pair. Solve this problem using both approaches, i.e. using tuple matching and by explicit use of the so-called projectors fst and snd. 8. The operators ==, /=, <, >, <=, >= also work on tuples. To see how, try the following: (a) (b) (c) ( 1, 2 ) < ( 1, 4 ) ( ( 4, 9 ), ( 3, 1 ) ) > ( ( 1, 10 ), ( 9, 4 ) ) ( 'a', 5, ( 3, 'b' ) ) < ( 'a', 5, ( 3, 'c' ) ) Remark: The ordering used by Haskell's comparison operators is lexicographical - it works from left to right just like the ordering used in a dictionary. Functional Programming in Haskell Unassessed Exercise Sheet 2: Functions For each function you define enter its type as well. As an additional exercise, when you are happy that your function is correct, delete the type definition using the editor and then return to the Hugs system. The Hugs system will automatically work out (infer) the type of your function and you can check this by typing :type <fun> where <fun> is the name of your function. In some cases you may be surprised to see that the inferred type is different to the type you originally wrote down. You may be able to work out what its doing in these cases; if not the later lectures will help to explain. 1. Write a function adddigit of two arguments which will add a single-digit integer onto the right-hand end of an arbitrary-size integer. For example, adddigit 123 4 should evaluate to 1234. Ignore the possibility of integer overflow. 2. Write a function convert which converts a temperature given in oC to the equivalent temperature in oF. (To convert from oF to oC the rule is to subtract 32 from the temperature in oF and then multiply the result by 5/9.) 3. Given the type synonym type Vertex = ( Float, Float ) write a function distance :: Vertex -> Vertex -> Float that will calculate the distance between two points, each represented as a Vertex. Using the distance function write a function triArea :: Vertex -> Vertex -> Vertex -> Float that will calculate the area of the triangle formed by the three given vertices. Note that a triangle whose sides are of length a, b and c has an area given by s(s a)( s b)( s c) where s (a b c) / 2 . 4. The factorial of a non-negative integer n is denoted as n! and defined as: n (n-1) (n-2) (n-3) … 2 1 0! is defined to be 1. Write a function fact which defines the factorial of a non- negative integer. Ignore the possibility of integer overflow. 5. Write a function remainder which defines the remainder after integer division. Implement the division by repeated subtraction (i.e. do not use the predefined functions div and mod). Ignore the possibility of division by zero. 6. Write a function quotient which defines integer division using repeated subtraction. Ignore division by zero. 7. The Fibonacci numbers are defined by the recurrence F0 = 0 F1 = 1 Fn = Fn-1 + Fn-2, n>1 Write a Haskell function fib that, given an integer n=0, 1, 2, ... returns the nth Fibonacci number by encoding the above recurrence directly. If you think about the way a call to this function will be evaluated you will notice that there is an enormous degree of redundancy. For a given n, he function call Fn-2 is made twice, the call Fn-3 three times, Fn-4 five times etc. (Notice any pattern?!) A much more efficient way to compute the nth Fibonacci number is to use an auxiliary function that includes as arguments the kth and the (k+1)th Fibonacci numbers for some number k. The idea is that the recursive call is made with the (k+1)th and the (k+2)th Fibonacci numbers can you see how to generate them from the kth and the (k+1)th? You will also need to carry a third argument which keeps count of how many (more) numbers in the sequence to calculate before arriving at the required (nth) one. Define an auxiliary function fib' which works in this way and redefine the original fib function to call fib' appropriately. 8. The Golden Ratio, G, is a beautiful number discovered by the ancient Greeks and used to proportion the Parthenon and other ancient buildings. The golden ratio has this property: take a rectangle whose sides (long:short) are in the golden ratio. Cut out the largest square you can and the sides of the rectangle that remains are also in the golden ratio. From this you should be able 1 5 to work out that G (do it!). Curiously, it can also be shown that the ratio of 2 F consecutive Fibonacci numbers converges on the golden ratio, i.e. lim n n1 G . Inspired Fn by your earlier fib' function build a function gRatio which takes a threshold value e of type F Float and which returns the value of n 2 where n is the smallest integer satisfying Fn 1 Fn 1 Fn 2 e . Hint: notice that you now need three consecutive Fibonacci numbers instead Fn Fn 1 of just two. The absolute value of a number (ignoring its sign, that is) can be obtained using Haskell's abs function. Also, to perform the division above, you must first cast the integers representing the various Fibonacci numbers into floating-point numbers using the built-in function fromInt. For now you can think of this function as having type fromInt :: Int -> Float. The true picture will emerge later in the course. 9. A decimal number d may be converted to binary representation by the following algorithm: i. If d is less than 2, its binary representation is just d. ii. Otherwise, divide by 2. The remainder gives the last (rightmost) digit of the binary representation, which is either 0 or 1. iii. The preceding digits of the binary representation are given by the binary representation of the quotient from step (b). Write a function binary which takes an integer and converts it to its binary representation. This will be a decimal number consisting only of the digits 0 and 1. As an example, binary 19 should produce the answer 10011. 10. Write a function newbase of two arguments which converts a number to its representation in some specified base 10. The value produced will be a decimal number containing only the digits which are valid in the new base. This is a generalisation of the binary function above. 11. The number of arrangements (permutations) of r objects selected from a larger set of n objects is denoted as nPr and defined as: n (n-1) (n-2) … (n-r+1) n! or ______ (n-r)! Define a recursive function perm such that perm n r evaluates nPr (do not use fact) 12. Given an integer argument the built-in function succ returns the next largest integer. For example, succ 4 returns 5. The function pred does the opposite. Write a function add2 which will add two non-negative numbers using succ and pred only. Use multiple recursion equations with pattern matching rather than conditionals. Remember that there are two parameters, each of which can be either zero or non-zero, so a strict definition will require four equations. Can the number of equations be reduced? 13. Write a recursive function larger to determine the larger of two positive integers. Use multiple recursion equations and do not use conditional expressions. Hint: write down the left-hand sides of the four recursion equations first. 14. Write a recursive function chop which takes an n-digit number and which uses repeated subtraction to define a pair of numbers which are the first n-1 digits and the last digit respectively. Do not use div or mod. 15. Write a function concatenate which concatenates the digits of two non-zero integers. concatenate 123 456 evaluates to 123456 but concatenate 123 0 evaluates to 123. Use the earlier function chop. Estimate the cost of your concatenate function in terms of the number of multiplications and subtractions it performs. Functional Programming in Haskell Unassessed Exercise Sheet 3: Lists and Higher-order Functions Many of these questions concern some of Haskell's (first-order) built-in list processing functions. By the end of the course you should be familiar with all of them and be able to use them to best advantage. length :: [ a ] -> Int The number of items in a list (!!) :: [ a ] -> Int -> a xs !! n returns the nth element of xs starting at index 0 null :: [ a ] -> Bool True if the list is empty; False otherwise (++) :: [ a ] -> [ a ] -> [ a ] Joins (appends) two lists head :: [ a ] -> a Returns the first item in the list (equivalent to index 0) tail :: [ a ] -> [ a ] Returns the list with the first item removed take :: Int -> [ a ] -> [ a ] take n xs returns the first n items in xs drop :: Int -> [ a ] -> [ a ] drop n xs returns the rest of xs after the first n items have been removed zip :: [ a ] -> [ b ] -> [ ( a, b ) ] Zips the elements of the two lists pairwise unzip :: [ ( a, b ) ] -> ( [ a ], [ b ] ) Opposite of zip The following are list generalisations of the functions +, *, &&, ||, ++, max and min respectively: sum :: [ Int ] -> Int Returns the sum of the elements of the list (also works with Integer, Float and Double types) product :: [ Int ] -> Int As above, but returns the product and :: [ Bool ] -> Bool True iff every element of the list is True or :: [ Bool ] -> Bool False iff every element of the list is False concat :: [ [ a ] ] -> [ a ] Joins the elements of a list of lists maximum :: [ Int ] -> Int Delivers the largest element in the given list (also works with Integer, Float and Double types) minimum :: [ Int ] -> Int As above but delivers the smallest element Now the higher-order functions: map :: ( a -> b ) -> [ a ] -> [ b ] Applies the given function to each element of the given list filter :: ( a -> Bool ) -> [ a ] -> [ a ] Returns a list of those elements in the given list which satisfy the given predicate zipWith :: ( a -> b -> c ) - [ a ] -> [ b ] -> [ c ] Applies the given binary function pairwise to the elements of the two lists foldr1 :: [ a -> a -> a ) -> [ a ] -> a Returns the result of inserting the binary operator, in right-associative fashion, in between each element of the given list. foldl1 does the same but associates to the left. foldr :: [ a -> b -> b ) -> b -> [ a ] -> b As above but with a separate right unit. foldl does the same but associates to the left. 1. By inspection (check them if you like by typing them into Hugs), determine whether the following are correctly typed. If they are, determine the resulting value. If not explain the cause of the type error. (a) (b) (c) (d) (e) (f) (g) (h) (i) (j) (k) (l) (m) (n) (o) (p) (q) (r) (s) (t) (u) "H" : [ 'a', 's', 'k', 'e', 'l', 'l' ] ( 'o' : ['n'] ) ++ "going" "Lug" ++ ( 'w' : "orm" ) if "a" == ['a'] then [] else "two" "let xs = "let xs" in length [xs] tail "emu" : drop 2 "much" head [ "gasket" ] zip "12" ( 1 : [ 2 ] ) tail ( (1,1), (2,2), (3,3) ) head [ 1, (2,3) ] /= head [ (2,3), 1 ]] length [] + length [[]] + length [[[]]] null ( [ "", "1", "11", "111" ] !! 1 ) zip [ 5, 3, length [] ] [ ( True, False, True ) ] unzip [ ( 'b', 'd' ), ( 'a', 'o' ), ( 'd', 'g' ) ] and [ 1 == 1, 'a' == 'a', True /= False ] sum [ length "one", [ 2, 3 ] !! 0, 4 ] minimum [ ( 'b', 1 ), ( 'a', 2 ), ( 'd', 3 ) ] maximum zip "abc" [ 1, 2, 3 ] concat [ tail [ "is", "not", "with", "standing" ] ] map head [ 1..n ] filter null ( map tail [ "a", "ab", "abc" ] ) (v) (w) (x) (y) (z) foldr1 (||) ( map even [ 9, 6, 8 , 2 ] ) zipWith (:) "zip" "with" foldr max 0 [ 0, 1 ] foldr maximum [0] [ [0,1], [3,2] ] zipWith (&&) [ True ] ( filter id [ True, False ] ) 2. Provide recursive definitions for each of the built-in functions defined above. (Most of these will have been covered during the lectures; make sure that you can write them all this way.) Note that to test them you will have to use different names to those used in the prelude, e.g. myLength, myMaximum etc. 3. The operators ==, /=, <, >, <=, >= also work on lists. The 'dictionary' style (lexicographical) ordering is perhaps more apparent here. Try the following: (a) (b) (c) (d) (e) (f) "Equals" == "Equals" "dictionary" <= "dictator" "False" > "Falsehood" [ 1, 1, 1 ] < [ 2, 2, 2 ] [ 1, 2, 3 ] < [ 1, 1, 5 ] [ ( 1, "way" ) ] < [ ( 2, "ways" ), ( 3, "ways" ) ] Now write a function precedes (equivalent to (<)) which takes two Strings and evaluates to True if the first is lexicographically less than the second. 4. Write a function pos to find the position of a specified Int in a list of Ints. Assume that the integers are indexed from 0 and that the specified character will always be found). pos '4' [ 3, 4, 0, 1 ] evaluates to 1. Using pos define quicksort :: [ Int ] -> [ Int ]. The simplest version of this famous algorithm works by using the first element of a list to partition the remainder of the list into two sublists: those smaller than (or equal to) the head element, and those greater than it. The two sublists are sorted recursively and the resulting lists joined together. Don't forget to add the head element back! 5. Write a function twoSame :: [ Int ] -> Bool which delivers True iff the given list of integers contains at least one duplicate element. 6. Two anagrams can be considered to define a transposition of the characters of a third string. For instance, "abcde" and "eabcd" define a transposition in which the last character of a 5-character string is moved to the start. Define a function transpose to transpose the characters of a string as specified by two anagrams. transpose "UVWXYZ" "fedcba" "ecabdf" should evaluate to "VXZYWU". Hint: use pos and !!, noting that all three strings are the same length and that each character of the second string is unique and appears exactly once in the third. 7. Write a function substring :: String -> String -> Bool which returns True iff the first given string is a substring of the second, e.g. substring "sub" "insubordinate" evaluates to True whilst substring "not" "tonight" evaluates to False. Hint: one way to do this is to first generate the list of all suffixes of the second string and then compare the first string with the appropriate prefix of each suffix. The overall result is True iff at least one of these comparisons succeeds (use the or function). 8. Write a polymorphic function perms which generates all permutations of a given list. 9. Write a polymorphic function right which will insert a single item at the right-hand end of a list of items. right 's' "Hope" evaluates to "Hopes". 10. Write a polymorphic function backwards which uses right to reverse a list of objects of arbitrary type. For example, backwards "bonk" evaluates to "knob". Estimate the cost of your function in terms of the number of ':' operations it performs to reverse a list of length n. (Note: this function is called reverse in the Haskell prelude.) 11. Now modify your backwards function so that it uses repeated applications of ':' to an accumulating parameter instead of using right. This requires you to define an auxiliary function with one extra argument to represent the “accumulator” (this will be [] when initally called). Estimate the cost of the new version of the function in terms of the number of ':' operations it performs to reverse a list of length n. 12. Write a function nextWord which given a list of characters s (i.e. a String) returns a pair consisiting of the next word in s and the remaining characters in s after the first word has been removed. Words are assumed to be separated one or more whitespace characters (' ', '\t', '\n'). Note: if you wish you may use the built-in function isSpace. 13. Write a function splitUp which returns the list of words contained in a given String. Use the function nextWord defined above. 14. Write a function squash :: ( a -> a -> b ) -> [ a ] -> [ b ] which applies a given function to adjacent elements of a list, e.g.. squash f [ x1, x2, x3, x4 ] -> [ f x1 x2, f x2 x3, f x3 x4 ]. Implement the function 1. Using explicit recursion and pattern matching and 2. In terms of zipWith (hint: make use of the tail function). 15. Write a function converge :: ( a -> a -> Bool ) -> [ a ] -> a which searches for convergence in a given list of values. It should apply its given function to adjacent elements of the list until the function yields True, in which case the result is the first of the two convergent values. For example, converge (==) [ 1, 2, 3, 4, 5, 5, 5 ] should return 5. If no limit is found before the list runs out, print an error message. 16. Write a function limit :: ( a -> a -> Bool ) -> [ a ] -> [ a ] which again checks for convergence but this time returns the list elements up to the point where the convergence function delivers True. This is a generalisation of the takeWhile function. 1 Mathematics tells us that if 0 r 1 then r n . Use this to test your limit function with 1 r n 0 an expression of the form sum ( limit f ( map (r^) [0..] ) for some convergence function f. 17. Sometimes we need to compose a single-argument function with a binary function, as in ( succ . (+) ) 4 7, (yielding 12) for example. However, Haskell's (.) function will only compose two single-argument functions. Define a new composition operator <.> that will allow compositions of the above form. What is the most general type of <.>? 18. Using '.', foldr, map and the identity function id, write a function pipeline which given a list of functions, each of type a -> a will form a pipeline function of type [ a ] -> [ a ]. In such a pipeline, each function in the original function list is applied in turn to each element of the input (assume the functions are applied from right to left in this case). You can imagine this as being like a conveyor belt system in a factory where goods are assembled in a fixed number of processing steps as they pass down a conveyor belt. Each process performs a part of the assembly and passes the (partially completed) goods on to the next process. Test your function by forming a pipeline from the function list [ (+ 1), (* 2), pred ] with the resulting pipeline being applied to the input list [ 1, 2, 3 ]. Hint: Notice that if f :: a -> a then map f is a function of type [ a ] -> [ a ]. Functional Programming in Haskell Unassessed Exercise Sheet 4: User-Defined Data Types 1. Define a data type Shape suitable for representing arbitrary triangles, whose dimensions are specified by the length of the triangle's three sides (all Floats), squares, whose dimensions are given by a single Float, and circles, each specified by its radius (again a Float). Define a function area :: Shape -> Float which returns the area of a given Shape. Recall: The area of a triangle with dimensions a, b, c is given by: A = sqrt( s (s-a) (s-b) (s-c) ) where s = ½(a+b+c) 2. Now augment Shape with (convex) polygons, specified by a list of the polygon vertices ((x,y) coordinates in the Cartesian plane). Augment the area function accordingly. Note: the area of a convex polygon is the area of the triangle formed by its first three vertices plus the area of the polygon remaining when the triangle has been removed from the polygon. Note: the length of the line joining vertex (x,y) with vertex (x',y') is given by L = sqrt( (x-x')^2 + (y-y')^2 ) 3. Define a type synonym Date suitable for representing calendar dates. Write a function age which given the birth date of a person and the current date returns the person’s age in years as an Int. 4. Define a new data type Possibly which can represent success/failure outcomes. Failure should be represented as a parameterless constructor whilst success should be a constructor that carries with it a result value of arbitrary type (Possibly should be polymorphic). Define a function tableLookUp :: a -> [ ( a, b ) ] -> Possibly b which searches for an item in a table of item/value pairs. If the item is found the lookup should succeed with the corresponding value; otherwise it should report failure.1 5. A university Computing department has two types of employee: Teaching staff and Support staff. Teaching staff have an associated research section which is either Systems, Software or Theory, and a list of courses that they teach. Each course is identified by a unique number. Support staff are classified as either Administrative or Technical. The department’s staff database comprises a list of staff records described by the Haskell type synonym: Database = [ Ustaff ] 1 When you have completed the solution take a look at the type Maybe and the function lookup in the Haskell prelude. Possibly and tableLookUp should behave identically! Associated with each employee is also a basic information record comprising the employees name, sex (male or female), date of birth and salary. Define a data type Sex suitable for representing a person’s sex and define a type synonym Empdata for representing each basic information record. Finally define an algebraic data type for Ustaff. Remark: There is no unique way of representing the database, as described. You may find that your solution is different to the model answer. 6. For the above Database write Haskell functions to compute the following: (a) (b) (c) The total number of support staff The name of the teacher who teaches a given course The total salary bill for the department (this is ususally a very small number!) Hints: You may find it useful to define the following auxiliary functions: • name and salary for returning the name and salary of a given Ustaff (functions that pick out elements of a tuple like this are sometimes called projector functions) • isSupport which will return True iff a given Ustaff is a support staff • teaches, which given a course number and a Ustaff delivers True iff the given Ustaff teaches the given course 7. Define a polymorphic data type for describing binary trees in which all values are stored at the leaves; there should be no concept of an “empty” tree. Write a function build which will construct a balanced binary tree from a non-empty list of numbers by using splitAt. You could issue an error message if build is applied to an empty list. Remark: A balanced binary tree has the property that at each internal node the sizes of the left and right subtrees differs by at most 1. 8. Write a function ends which will convert a binary tree to a list preserving a left-to-right ordering of the numbers at the fringe of the tree. 9. Write a function swap which will interchange the subtrees of a binary tree at every level. For a list xs how does the value of ends ( swap ( build xs ) ) relate to xs? 10. A queue is a data structure which can contain zero or more elements, ordered by their arrival times. Define a data type to represent queues of customers represented by words containing their names. Write functions nobody to create an empty queue, arrive to add a new customer to the rear of an existing queue, first to find the first (least recently arrived) customer in a queue and serve to remove the first customer from the front of a non-empty queue. 11. Design a data type Time which is capable of representing time in two ways: either in 24hour format (for example 1356hrs) or in conventional hours and minutes, with an additional flag to indicate whether the time is am or pm (use an additional enumerated data type AmPm to represent the two possibilities). Use deriving to enable times to be both displayed and compared for structural equality. Notice that at this stage the derived definition of '==' does not “know” that the times 1400hrs in 24-hour format and 2-00pm represent the same time! 12. Write a function to24 which, given a time in conventional format will convert it to 24hour format. 13. Using to24 write a function equaltime which given two times in either format returns True if the times are the same; False otherwise. For example given the representations of 1514hrs and 3:14pm equaltime should return True. 14. Now delete the deriving statement and explicitly add Time as an instance of the class Eq. At the same time define '==' on Times which delivers True if two times are equal regardless of the format of the time. Test your implementation by comparing various Times for both equality and inequality (recall that '/=' is defined by default in terms of '==' in the definition of class Eq). 15. Now make Time an instance of the class Show and provide a definition of the function showsPrec (see below) for displaying Times (see below). A time in 24-hour format should be displayed in the form “xxxxHRS” where xxxx is the four-digit 24-hour time. For a time in the conventional format, hours (hh) and minutes (mm) should be displayed in the form “hh:mmAM” for times before midday, and “hh:mmPM” for times after midday, with the special cases that 1200pm and 12-00am should be displayed as “Midday” and “Midnight” respectively. Test your implementation by typing various expressions of type Time on the Haskell command line. Remark: The class Show has a member function showsPrec. When a new type T is made an instance of Show the showsPrec function defines how to convert an object of type T into a (printable) string. The Haskell system uses this function to print representations of objects, as in: Main> “Hello” “Hello” Main> [ Mon, Tues ] [ Mon, Tues ] showsPrec is of type Int -> T -> String -> String. The first argument is a precedence which can be used to print representations using the minimum number of brackets. Do not worry about this here; simply ignore it! The String argument is an accumulation of the result string that will form the final printable representation. All you have to do here is append to this your chosen representation of T; for the Time data type you will need two rules, one for each time format. Note that the result list is accumulated from right to left, so you will need to append your representation as the leftmost argument of '++'. Q16-18. The following questions are quite hard an invite you to think about higher-order functions over trees, rather than those over lists that you have seen so far. There is a short introduction to this in the lecture notes, although we will not be covering it in the lectures themselves. 16. Define a new polymorphic data type Tree3 which stores values both at the internal nodes and the leaves, and for which there is an Empty data constructor for representing empty trees. Arrange it so that values at the nodes may have a different type to those at the leaves. 17. Define a function map3 which maps two functions over a given Tree3. The first function should be applied to values at the leaves and the second to values at the nodes. 18. Define the function fold3, a version of fold for objects of type Tree3 which takes two functions, one to transform a given leaf value and one to reduce an internal node. (You can best picture this as a transformation on a tree which replaces the empty tree by a given base case, and the leaf and node constructors with the supplied functions.) Define functions in terms of fold3 to do the following: (a) (b) (c) (d) (e) Count the number of leaves in a given tree (an empty tree should not be counted as a leaf) Sum the values in a given tree of Ints Perform a left-to-right in-order flattening of a tree to yield a list Perform a right-to-left in-order flattening of a tree which delivers the same result as (c) only in the reverse order (do this by modifying your folding functions; do not use a reverse function on the result from (c)!!) Search a tree of value-attribute pairs of the form (v,a) for all occurrences of a given value. The attributes associated with each such value in the tree should be returned as a list. Use an auxiliary function pass :: a -> ( a, b ) -> [ b ] which takes a value and a value-attribute pair and which returns the singleton list consisting of the attribute if the two values match; [] otherwise. APPENDIX: Summary of the Haskell Prelude Types and Synonyms data () = () deriving (Eq, Ord, Enum, Bounded) Instances: Read Show data [a] = [] | a : [a] deriving (Eq, Ord) Instances: Read Show Functor Monad MonadZero MonadPlus data (a,b) = (a,b) deriving (Eq, Ord, Bounded) Instances: Read Show data a->b Instances: Show data Bool = False | True deriving (Eq, Ord, Enum, Read, Show, Bounded) data Char Instances: Eq Ord Enum Read Show data Double Instances: Eq Ord Enum Read Show Num Real Fractional RealFrac Floating RealFloat data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show) type FilePath = String data Float Instances: Eq Ord Enum Read Show Num Real Fractional RealFrac Floating RealFloat data Int Instances: Eq Ord Enum Read Show Num Real Integral Bounded data Integer Instances: Eq Ord Enum Read Show Num Real Integral data IO a Instances: Show Functor Monad data IOError Instances: Show Eq data Maybe a = Nothing | Just a deriving (Eq, Ord, Read, Show) Instances: Functor Monad MonadZero MonadPlus data Ordering = LT | EQ | GT deriving (Eq, Ord, Enum, Read, Show, Bounded) type ReadS a = String -> [(a,String)] type ShowS = String -> String type String = [Char] data Void Constructors [] : (,) EQ False GT Just Left LT Nothing Right True Classes class class class class class (Fractional class (Num class class (Real a, Enum class class (MonadZero class (Monad class (Eq a, Show a, Eval class (Eq class class (Num a, Ord class (RealFrac a, Floating class (Real a, Fractional class a) => a) => a) => m) m) a) a) => => => => a) => a) => a) => Bounded a Enum a Eq a Eval a Floating a Fractional a Functor f Integral a Monad m MonadPlus m MonadZero m Num a Ord a Read a Real a RealFloat a RealFrac a Show a Functions and Methods (!!) :: [a] -> Int -> a [0,1,2] !! 1 = 1 ($) :: (a -> b) -> a -> b f x $ g y = f x (g y) (&&) :: Bool -> Bool -> Bool Boolean `and' (||) :: Bool -> Bool -> Bool Boolean `or' (*) :: Num a => a -> a -> a (**) :: Floating a => a -> a -> a (+) :: Num a => a -> a -> a (++) :: MonadPlus m => m a -> m a -> m a "abc" ++ "def" = "abdec" (-) :: Num a => a -> a -> a (.) :: (b -> c) -> (a -> b) -> a -> c Function composition (/) :: Fractional a => a -> a -> a (/=) :: Eq a => a -> a -> Bool not equal (<) :: Ord a => a -> a -> Bool (<=) :: Ord a => a -> a -> Bool (==) :: Eq a => a -> a -> Bool (>) :: Ord a => a -> a -> Bool (>=) :: Ord a => a -> a -> Bool (>>) :: m a -> m b -> m b Monadic binding (>>=) :: Monad m => m a -> (a -> m b) -> m b Monadic binding (^) :: (Num a, Integral b) => a -> b -> a (^^) :: (Fractional a, Integral b) => a -> b -> a negative exponent allowed abs :: Num a => a -> a accumulate :: Monad m => [m a] -> m [a] acos :: Floating a => a -> a acosh :: Floating a => a -> a all :: (a -> Bool) -> [a] -> Bool all (/= 'a') "cba" = False and :: [Bool] -> Bool and [True, True, True] = True any :: (a -> Bool) -> [a] -> Bool any (== 'c') "abc" = True appendFile :: FilePath -> String -> IO () applyM :: Monad m => (a -> m b) -> m a -> m b asTypeOf :: a -> a -> a Sort of a type cast asin :: Floating a => a -> a asinh :: Floating a => a -> a atan :: Floating a => a -> a atan2 :: RealFrac a => a -> a atanh :: Floating a => a -> a break :: (a -> Bool) -> [a] -> ([a], [a]) break (<2) [1,2,3] = ([1],[2,3]) catch :: IO a -> (IOError -> IO a) -> IO a ceiling :: (RealFrac a, Integral b) => a -> b compare :: Ord a => a -> a -> Ordering concat :: MonadPlus m => [m a] -> m a concat ["a","bc","d"] = "abcd" concatMap :: (a -> [b]) -> [a] -> [b] const :: a -> b -> a cos :: Floating a => a -> a cosh :: Floating a => a -> a curry :: ((a, b) -> c) -> a -> b -> c cycle :: [a] -> [a] cycle "abc" = "abcabcabc ..." decodeFloat :: RealFloat a => a -> (Integer, Int) div :: Integral a => a -> a -> a divMod :: Integral a => a -> a -> (a, a) drop :: Int -> [a] -> [a] drop 2 "abcd" = "cd" dropWhile :: (a -> Bool) -> [a] -> [a] dropWhile (>3) [5,3,5] = [3,5] elem :: Eq a => a -> [a] -> Bool 'a' `Elem` "abc" = True encodeFloat :: RealFloat a => Integer -> Int -> a enumFrom :: Enum a => a -> [a] [n..] enumFromThen :: Enum a => a -> a -> [a] [m,n..] enumFromThenTo :: Enum a => a -> a -> a -> [a] [m,n..o] enumFromTo :: Enum a => a -> a -> [a] [m..n] error :: String -> a even :: Integral a => a -> Bool exp :: Floating a => a -> a exponent :: RealFloat a => a -> Int fail :: IOError -> IO a filter :: MonadZero m => (a -> Bool) -> m a -> m a filter isUpper "AbCd" = "AB" flip :: (a -> b -> c) -> (b -> a -> c) floatDigits :: RealFloat a => a -> Int floatRadix :: RealFloat a => a -> Integer floatRange :: RealFloat a => a -> (Int, Int) floor :: (RealFrac a, Integral b) => a -> b foldl :: (a -> b -> a) -> a -> [b] -> a foldl (+) 0 [a,b,c] = ((0+a)+b)+c foldl1 :: (a -> a -> a) -> [a] -> a foldl1 (+) [a,b,c] = (a+b)+c foldr :: (a -> b -> b) -> b -> [a] -> b foldr (+) 0 [a,b,c] = a+(b+(c+0)) foldr1 :: (a -> a -> a) -> [a] -> a foldr (+) [a,b,c] = a+(b+c) fromEnum :: Enum a => a -> Int fromInteger :: Num a => Integer -> a fromIntegral :: (Integral a, Num b) => a -> b fromRational :: Fractional a => Rational -> a fromRealFrac :: (RealFrac a, Fractional b) => a -> b fst :: (a, b) -> a gcd :: (Integral a) => a -> a -> a getChar :: IO Char eof generates an IOError getContents :: IO String getLine :: IO Char eof generates an IOError guard :: MonadZero m => Bool -> m () head :: [a] -> a id :: a -> a init :: [a] -> [a] init "abcd" = "abc" interact :: (String -> String) -> IO () isDenormalized :: RealFloat a => a -> Bool isIEEE :: RealFloat a => a -> Bool isInfinite :: RealFloat a => a -> Bool isNaN :: RealFloat a => a -> Bool isNegativeZero :: RealFloat a => a -> Bool iterate :: (a -> a) -> a -> [a] iterate (++ " ") "" = ["", " ", " ",...] last :: [a] -> a last "abcde" = "e" lcm :: Integral a => a -> a -> a length :: [a] -> Int length "Abc" = 3 lex :: ReadS String lex "abc def" = [("abc"," def")] lines :: String -> [String] log :: Floating a => a -> a logBase :: Floating a => a -> a -> a lookup :: Eq a => a -> [(a, b)] -> Maybe b map :: Functor f => (a -> b) -> f a -> f b mapM :: Monad m => (a -> m b) -> [a] -> m [b] mapM_ :: Monad m => (a -> m b) -> [a] -> m () max :: Ord a => a -> a -> a maxBound :: Bounded a => a maximum :: Ord a => [a] -> a maybe :: b -> (a -> b) -> Maybe a -> b maybe 0 (+1) (Just 1) = 2 min :: Ord a => a -> a -> a minBound :: Bounded a => a minimum :: Ord a => [a] -> a mod :: Integral a => a -> a -> a negate :: Num a => a -> a not :: Bool -> Bool notElem :: Eq a => a -> [a] -> Bool null :: [a] -> Bool odd :: Integral a => a -> Bool or :: [Bool] -> Bool otherwise :: Bool pi :: Floating a => a pred :: Enum a => a -> a pred True = False print :: Show a => IO () adds a newline product :: Num a => [a] -> a properFraction :: (RealFrac a, Integral b) => a -> (b, a) putChar :: Char -> IO () putStr :: String -> IO () putStrLn :: String -> IO () adds a newline quot :: Integral a => a -> a -> a quotRem :: Integral a => a -> a -> (a, a) read :: Read a => String -> a readFile :: FilePath -> IO String readIO :: Read a => String -> IO a fails with IOError readList :: Read a => ReadS [a] readLn :: Read a => IO a readParen :: Bool -> ReadS a -> ReadS a reads :: Read a => ReadS a reads "1 2" :: [(Int,String)] = [(1," 2")] readsPrec :: Read a => Int -> ReadS a recip :: Fractional a => a -> a rem :: Integral a => a -> a -> a repeat :: a -> [a] repeat 'a' = "aaaaaaaaa..." replicate :: Int -> a -> [a] replicate 4 'a' = "aaaa" return :: Monad m => a -> m a reverse :: [a] -> [a] reverse "abc" = "cba" round :: (RealFrac a, Integral b) => a -> b scaleFloat :: RealFloat a => Int -> a -> a scanl :: (a -> b -> a) -> a -> [b] -> [a] scanl (+) 0 [1,2,3] = [1,3,6] scanl1 :: (a -> a -> a) -> [a] -> [a] scanl1 (+) [1,2,3] = [1,3,6] scanr :: (a -> b -> b) -> b -> [a] -> [b] scanr (+) 0 [1,2,3] = [6,5,1,0] scanr1 :: (a -> a -> a) -> [a] -> [a] scanr (+) [1,2,3] = [6,5,1] seq :: Eval a => a -> a -> b sequence :: Monad m => [m a] -> m () do operations in sequence show :: Show a => a -> String showChar :: Char -> ShowS showList :: Show a => [a] -> ShowS showParen :: Bool -> ShowS -> ShowS showString :: String -> ShowS shows :: Show a => a -> ShowS showsPrec :: Show a => Int -> a -> ShowS significand :: RealFloat a => a -> a signum :: Num a => a -> a sin :: Floating a => a -> a sinh :: Floating a => a -> a snd :: (a, b) -> b span :: (a -> Bool) -> [a] -> ([a], [a]) span isAlpha "ab cd" = ("ab"," cd") splitAt :: Int -> [a] -> ([a], [a]) splitAt 2 "abcdef" = ("ab","cdef") sqrt :: Floating a => a -> a strict :: Eval a => (a -> b) -> (a -> b) subtract :: Num a => a -> a -> a succ :: Enum a => a -> a succ False = True sum :: Num a => [a] -> a sum [1,2,3] = 6 tail :: [a] -> [a] tail "abc" = "bc" take :: Int -> [a] -> [a] take 3 "abcde" = "abc" takeWhile :: (a -> Bool) -> [a] -> [a] takeWhile (> 2) [3,2,1] = [3] tan :: Floating a => a -> a tanh :: Floating a => a -> a toEnum :: Enum a => Int -> a toEnum 0 :: Bool = False toInteger :: Integral a => a -> Integer toRational :: Real a => a -> Rational truncate :: (RealFrac a, Integral b) => a -> b uncurry :: (a -> b -> c) -> ((a, b) -> c) undefined :: a unlines :: [String] -> String until :: (a -> Bool) -> (a -> a) -> a -> a until (> 3) (+ 2) 0 = 4 unwords :: [String] -> String unzip :: [(a, b)] -> ([a], [b]) unzip [('a','b'),('c','d')] = ["ac",bd"] unzip3 :: [(a, b, c)] -> ([a], [b], [c]) userError :: String -> IOError words :: String -> [String] words "ab d as+3" = ["ab","d","as+3"] writeFile :: FilePath -> String -> IO () zero :: MonadZero m => m a zip :: [a] -> [b] -> [(a, b)] zip "abc" "de" = [('a','d'), ('b',e')] zip3 :: [a] -> [b] -> [c] -> [(a, b, c)] zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] zipWith (+) [1,2] [3,4] = [4,7] zipWith3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]