Homework 2
Type classes
This homework is written in literate Haskell; you can download the raw source to fill in yourself. You’re welcome to submit literate Haskell yourself, or to start fresh in a new file, literate or not.
Please submit homeworks via the new submission page.
This will be our first of two “pair programming” homeworks, where I expect you and your partner to do the work together, submitting a single file listing both collaborators. It doesn’t matter who initiaties the upload as long as everyone is on the list.
In this homework, we’re going to use Haskell more earnestly. We’ll start using some of its standard library’s functions and datatypes—we’ll even try defining our own datatypes.
Unless I say otherwise, you’re free to use any functions from the Prelude.
Problem 1: arithmetic expressions
Our first language will be a simple one: arithmetic expressions using +, *, and negation.
(a) 5 points
Write a Show
instance for ArithExp
, which amounts to a function show :: ArithExp -> String
. You do not need to write type signatures for functions in type class instances.
Your function should print a parseable expression, i.e., one that you could copy and paste back in to Haskell to regenerate the original term. For example, show (Num 5)
should yield the string "Num 5"
, while show (Neg (Plus (Num 1) (Num 1)))
should yield the string "Neg (Plus (Num 1) (Num 1))"
.
(b) 5 points
Write an Eq
instance for ArithExp
, defining a function (==) :: ArithExp -> ArithExp -> Bool
; use the standard “equal structures are equal” interpretation.
(c) 10 points
We’re going to write an interpreter, which takes an arithmetic expression and evaluates it to a number. The general strategy here is the same as when we wrote naturally recursive functions over lists: break down each case of the datatype definition and use recursion on subparts.
For example, eval (Plus (Num 42) (Neg (Num 42)))
should yield 0
.
-- | Tests for arithmetic evaluation. Write your own!
-- >>> eval (Plus (Num 42) (Neg (Num 42)))
-- 0
eval :: ArithExp -> Int
eval = undefined
(d) 10 points
Let’s extend our language to support subtraction—now we’re really cooking! Note that we let Haskell derive a “parseable” show instance for us.
data ArithExp' =
Num' Int
| Plus' ArithExp' ArithExp'
| Sub' ArithExp' ArithExp'
| Times' ArithExp' ArithExp'
| Neg' ArithExp'
deriving Show
But wait: we should be able to encode subtraction using what we have, giving us a very nice evaluation function.
Write a function that will translate this extended language to our original language—make sure that eval'
does the right thing.
(e) 5 points
Write a non-standard Eq
instance for ArithExp'
, where e1 == e2
iff they evaluate to the same number, e.g., (Num' 2) == (Plus' (Num' 1) (Num' 1))
should return `True.
Write a non-standard Ord
instance for ArithExp'
that goes with the Eq
instance, i.e., e1 < e2
iff e1
evaluates to a lower number than e2
, etc.
Problem 2: Setlike
(10pts)
Here is a type class Setlike
. A given type constructor f
, of kind * -> *
, is Setlike
if we can implement the following methods for it. (See Listlike
in the notes for lecture.) Your code will be tested only using methods from Setlike
, i.e., you don’t have to handle arbitrary f a
, only those constructed using, e.g., emp
and insert
.
class Setlike f where
emp :: f a
singleton :: a -> f a
union :: Ord a => f a -> f a -> f a
union = fold insert
insert :: Ord a => a -> f a -> f a
insert = union . singleton
delete :: Ord a => a -> f a -> f a
delete x s = fold (\y s' -> if x == y then s' else insert y s') emp s
isEmpty :: f a -> Bool
isEmpty = (==0) . size
size :: f a -> Int
size = fold (\_ count -> count + 1) 0
isIn :: Ord a => a -> f a -> Bool
isIn x s = maybe False (const True) $ getElem x s
getElem :: Ord a => a -> f a -> Maybe a
fold :: (a -> b -> b) -> b -> f a -> b
toAscList :: f a -> [a] -- must return the list sorted ascending
toAscList = fold (:) []
In the rest of this problem, you’ll define some instances for Setlike
and write some code using the Setlike
interface. Please write the best code you can. Setlike
has some default definitions, but sometimes you can write a function that’s more efficient than the default. Do it. Write good code.
Hint: look carefully at the types, and notice that sometimes we have an Ord
constraint and sometimes we don’t. I did that deliberately… what am I trying to tell you about how your code should work?
Define an instance of Setlike
for lists. Here’s an example that should work when you’re done—it should be the set {0,2,4,6,8} (note that I’m using math notation for sets—yours will probably print out as a list)..
-- |
-- >>> 6 `isIn` evensUpToTen
-- True
--
-- >>> 42 `isIn` evensUpToTen
-- False
evensUpToTen :: [Int]
evensUpToTen = foldr insert emp [0,2,4,6,8]
Here’s a type of binary trees. Define a Setlike
for BSTs, using binary search algorithms. Write good code. I expect insertion, lookup, and deletion to all be O(log n). No need to do any balancing, though.
Write Eq
and Show
instances for BSTs. These might be easier to write using the functions below. Keep in mind what a programmer might expect out of these definitions: if BSTs are being used for sets (and maps, below), what notion of equality might we want for them?
Write the following set functions. You’ll have to use the Setlike
interface, since you won’t know which implementation you get.
fromList
should convert a list to a set.
difference
should compute the set difference: X - Y = { x in X | x not in Y }.
subset
should determine whether the first set is a subset of the other one. X ⊆ Y iff ∀ x. x ∈ X implies x ∈ Y.
Problem 3: maps from sets (10pts)
Finally, let’s use sets to define maps—a classic data structure approach.
We’ll define a special notion of key-value pairs, KV k v
, with instances to force comparisons just on the key part.
newtype KV k v = KV { kv :: (k,v) }
instance Eq k => Eq (KV k v) where
(KV kv1) == (KV kv2) = fst kv1 == fst kv2
instance Ord k => Ord (KV k v) where
compare (KV kv1) (KV kv2) = compare (fst kv1) (fst kv2)
instance (Show k, Show v) => Show (KV k v) where
show (KV (k,v)) = show k ++ " |-> " ++ show v
Now define the following map functions that work with Setlike
.
-- |
-- >>> find 5 (emptyMap :: TreeMap Int Bool)
-- Nothing
-- >>> find 5 (extend 5 True (emptyMap :: TreeMap Int Bool))
-- Just True
-- >>> find 5 (extend 5 True (extend 5 False (emptyMap :: TreeMap Int Bool)))
-- Just True
emptyMap :: Setlike f => Map f k v
emptyMap = undefined
find :: (Setlike f, Ord k) => k -> Map f k v -> Maybe v
find k m = undefined
extend :: (Setlike f, Ord k) => k -> v -> Map f k v -> Map f k v
extend k v m = undefined
remove :: (Setlike f, Ord k) => k -> Map f k v -> Map f k v
remove k m = undefined
toAssocList :: Setlike f => Map f k v -> [(k,v)]
toAssocList = undefined
You’ll have to think hard about what to do for find
and remove
… what should v
be?
Problem 4: functors (15pts)
(a) 5pts
The n-ary tree, trie, or rose tree data structure is a tree with an arbitrary number of children at each node. We can define it simply in Haskell:
(Note that this definition subtly disagrees with the Wikipedia definition of rose trees by (a) having values at the leaves and (b) not having values at the nodes.)
Define a Functor
instance for RoseTree
.
(b) 5pts
Define a Functor
instance for BST
.
Give an example of a buggy behavior for your instance: this can either be a violation of the Functor
laws, or something else. Explain what the issue is.
(c) 5pts
What does the following function do? Explain it as best you can. Does it have a name in the Prelude?
Now rewrite it to use variable names, without any partial application.