Illinois Institute of Technology
Homework 4
4/2 p.3
Instructions
Haskell Programming
CS 440: Programming Languages and Translators Due Wed Apr 4, 11:59
No late assignments — Solution will be posted Thu Apr 5
- You can work together in groups of ≤ 4. Submit your work on Blackboard. Submit one copy. Include the names and A-IDs of everyone in the group on that copy. Submit under the name of one person in the group (doesn’t matter who).
- All the questions refer to Haskell programs; we’ll run them on fourier to test them. Write your answers into a *.hs file and submit that. If necessary, write non-code items as comments {- possibly over multiple lines -}
Questions [100 points total]
- [4 = 4 * 1 points] Give the (polymorphic) types of the following functions. (Figure them out yourself first, then enter them into ghci to make sure you’re right.) It’s ok to uniformly rename variables (e.g. a -> [a] and b -> [b] are equivalent).
:{
f = \x -> [x]
g = \x y z -> (x:y, y:z) S f g x = f x (g x) Kxy=x
:} - [3 points] Rewrite the following binding to make it syntactically correct. (Keep the num and denom names — don’t just start with 17 mod 3 and continue on from there.)
:{ X = num mod denom
:}
where
num = 17
denom = 3
3. [5 points] Fix the incorrect definition of function flipcase below. It’s supposed to take a character and flip it from lowercase to uppercase (or vice versa). If the character is not a letter, flipcase should just return its argument. E.g., we should get
map flipcase "abCDE0123;?!" == "ABcde0123;?!"
If you’re working in ghci, you should add the module Data.Char using :m + Data.Char at the ghci prompt before trying to define flipcase. (Or be forced to type in names like Data.Char.isLower.) Don’t use if-else expressions: Practice using patterns and guards.
CS 440: Programming Languages and Translators – 1 – © James Sasaki, 2018
Illinois Institute of Technology
Homework 4
:{ flipcase (isLower c) -> toUpper c
| isLower c = toUpper c
otherwise = c :}
- [10 points] Give the definition of a function nmap n f xs that works like map f xs but instead of calling f once on each member of xs, it does n calls of f. E.g., nmap 2 sqrt [16.0, 81.0] = [2.0, 3.0], since 16 = (2 ^ 2) ^ 2 and 81 = (3 ^ 2) ^ 2. Ifn≤0,nmapshould just returnxs.
- [10 = 2 + 8 points]
- Give a one-sentence description of what f defined below does. (Try running it on some data.)
- Rewrite f so that it doesn’t use dot (function composition) but does use an unnamed lambda.
:{ f :: ??? -- fill in the type of f, too f x = length . filter (== x) -- change this definition :}
- [7 points] Using reverse, tail, and function composition, write a definition for an init2 function that behaves like init. You don’t need recursion; the answer is one line long. In Haskell, function composition is done with infix dot (as in f . g).
- [10 points] Write a routine isAllDigits str that returns True iff the string is nonempty and all the characters in the string are decimal digits. You can use the Data.Char function isDigit c to see if a character is a digit. You can write your function recursively if you want, but it’s not required — check out the Data.List routines map and and. (In ghci, you’ll want to use :m + Data.Char Data.List)
- [20 points] The datatype Tree a below defines a binary tree with values of type a attached to the leaves and no values attached to interior nodes:
:{ -- we'll use :^: as an infix operator for constructing trees infixl :^: data Tree a = L a | Tree a :^: Tree a deriving (Eq, Show) sampleTree = L 1 :^: L 2 :^: (L 3 :^: L 4) :}
Write a function frontier :: Tree a → [a] that returns all the values of the leaves of a tree, in preorder. E.g., frontier sampleTree = [1,2,3,4].
- [31 = 6 + 25 points]
CS 440: Programming Languages and Translators – 2 – © James Sasaki, 2018
Illinois Institute of Technology Homework 4
- What are the two differences between the datatype declarations below? Discuss briefly and give a short example of each difference.
:{ data QueueOp a = ENQ a | DEQ data QueueOp a = ENQ a | DEQ deriving (Eq, Show) :}
- We’re going to model a queue of values, a list of queue operations, and a list of the results of the operations. First, take the second definition of QueueOp above and add the following type declarations.
:{ type Queue a = [a] type QInstrs a = [QueueOp a] type QResults a = [a] -- change [4/2] :}
We’re aiming for an evaluation function eval qInstrs that starts with an empty queue and applies a sequence of queue operations to it. It should return a pair: the queue that remains at the end and a list of the dequeue results. E.g., the code below should eventually set test = ([3], [1,2]).
:{ testInstrs = [ENQ 1, DEQ, ENQ 2, ENQ 3, DEQ] test = eval testInstrs :}
To do its work, eval calls a helper routine eval’: The call eval’ queue qInstrs qResults should apply the queue instructions to the given queue, appending dequeue results as it goes. For example,
eval’ [2,3] [DEQ] [1] should yield ([3], [1,2]) because dequeueing [2,3] results in a queue of [3], and the dequeue operation yields 2, which gets added to the partial results [1] to get [1,2].
-- [changes 4/2]
:{ eval :: QInstrs a -> (Queue a, QResults a) eval' :: Queue a -> QInstrs a -> QResults a -> (Queue a, QResults a)
eval qinstrs = eval' [] qinstrs [] where eval' queue [] results = (queue, results) :}
The eval routine above is actually complete; you need to add extend eval’ to handle ENQ and DEQ. Note: Dequeueing an empty queue should cause an error “Dequeue of empty queue” (or some such message).
CS 440: Programming Languages and Translators – 3 – © James Sasaki, 2018