Sale!

MP1 – Basic Haskell

$30.00

Category:
Rate this product

MP1 – Basic Haskell
===================

Objectives
==========

We will be using Haskell throughout the course to implement other programming
languages. That being said, the focus of this course is *not* on Haskell, but
rather on studying programming languages in general. You need to understand
basic Haskell before we can proceed with the rest of the course though; this MP
will test that understanding.

Goals
—–

– Part 1:
– Write recursive functions and definitions.
– Implement some set-theoretic functionality using Haskell lists.
– Use higher-order functions to compactly represent common code patterns.
– Part 2: Use and write Algebraic Data Types (ADTs) with some associated
operators.
– Manipulate linked lists and simple arithmetic expressions defined using
ADTs
– Define a binary tree ADT and implement a function on binary trees.
– Define a constant value ADT for a simple programming language and a
higher order function to manipulate those values.

Useful Reading
————–

If you are stuck on some problems, perhaps it’s time to read some of [Learn You
a Haskell for Great Good](http://learnyouahaskell.com/chapters). I would
recommend reading the whole book eventually, but if you’re crunched for time
Chapter 3 on Types and Typeclasses, Chapter 4 on Syntax in Functions, and
Chapter 8 on Making Your Own Types and Typeclases seem the most relevant. If
you’re still stuck on recursion problems, check out Chapter 5 on Recursion.

Getting Started
===============

Relevant Files
————–

In the directory `recursion/src` you’ll find `Lib.hs` with all the relevant code
for Part 1. In the directory `adt/src` you’ll find a file of the same name with
all the relevant code for Part 2. In each file, the first line `module Lib where`
says that what follows (the rest of the file in this case) belongs to the `Lib`
module. Haskell has a module system which allows for extensive code re-use.
Saying something like `import Data.List` imports the `List` module, for example.

Running Code
————

First, you have to `stack init` (you only need to do this once):

“` {.sh}
$ stack init
“`

To run your code, start GHCi with `stack ghci`:

“` {.sh}
$ stack ghci
– more output –
Ok, modules loaded: Lib.
*Lib>
“`

Testing Your Code
—————–

You can run the test-suite as you fill in the solutions for Part 1.

The following ensures the file will type-check, but the
corresponding `myFunction` test will fail.

“` {.haskell}
myFunction = undefined
“`

In Part 2, you must supply the Algebraic Data Type declarations at the end of the
problem-set correctly in order to compile the tests. If you are having trouble with
one declaration in particular, use the interpreter to define and debug each ADT
individually.

In each part, the test suite is compiled and executed by running `stack test`
in the project directory. `recursion` and `adt` for parts 1 and 2 respectively.

“` {.sh}
$ stack test
“`

It will tell you which test-suites you pass, fail, and have exceptions on. To
see an individual test-suite (so you can run the tests yourself by hand to see
where the failure happens), look in the file `test/Spec.hs`.

I Can’t Do Problem X
——————–

You can ask for help on Piazza or in office hours. If you’re really stuck and
don’t have time to fix it, you *must* still put in the type declaration and
define it as `undefined` so that it still compiles with our autograder. Not
doing so will result in loss of extra points from your total score. Remember
that course policy is that code which doesn’t compile receives a zero.

For example, if you cannot complete problem `app :: [a] -> [a] -> [a]`, make
sure you put the following code into your submission:

“` {.haskell}
app :: [a] -> [a] -> [a]
app = undefined
“`

Problems
========

Builtins:
: In general you cannot use the Haskell built-in functions. Especially if we
say that a function “should behave exactly like the Haskell built-in”, *you
cannot use that Haskell built-in*.
__If you modify or remove the following two import statements your submission
will not be scored!__
“`{.haskell}
import qualified Prelude as P
import Prelude hiding ( take, drop, … etc …
“`

Pattern-matching:
: You should try to use pattern-matching whenever possible (it’s more
efficient and easier to read). If you are using the functions\
`fst :: (a,b) -> a` or `snd :: (a,b) -> b` (for tuples)\
`head :: [a] -> a` or `tail :: [a] -> [a]` (for lists)\
chances are you can do it with pattern matching. We will take off points if
you use built-ins when it’s possible to pattern-match.

Helpers:
: You may write your own helper functions (inside or outside a
`where` clause). All the restrictions about allowable code (no
built-ins/using pattern matching) still apply to your helper functions.

Part 1: Recursion and Higher-Order Functions
============================================
> _include your definitions for the following problems in `mp1a-recursion/src/Lib.hs`_

Recursion
———

For these problems, you *may not* use higher-order functions. Instead, you
should use recursion to implement the stated functionality. If you do use
higher-order functions (or do not use recursion), you will receive no points.

### mytake

Write a function `mytake :: Int -> [a] -> [a]` which takes the first `n`
elements of a list, or the whole list if there are not `n` elements. It should
behave exactly like the Haskell built-in `take :: Int -> [a] -> [a]`.

“` {.haskell}
*Main> mytake 4 [2,4,56]
[2,4,56]
*Main> mytake 3 []
[]
*Main> mytake 1 [“hello”, “world”]
[“hello”]
*Main> mytake (-3) [1,2,3]
[]
“`

### mydrop

Write a function `mydrop :: Int -> [a] -> [a]` which drops the first `n`
elements of a list, or the whole list if there are not `n` elements. It should
behave exactly like the Haskell built-in `drop :: Int -> [a] -> [a]`.

“` {.haskell}
*Main> mydrop 3 [2,4,56,7]
[7]
*Main> mydrop 3 []
[]
*Main> mydrop 1 [“hello”, “world”]
[“world”]
*Main> mydrop (-3) [1,2,3]
[1,2,3]
“`

### rev

Write a function `rev :: [a] -> [a]` which reverses the input list. To get
credit, your solution must run in linear time. If you use the `(++)` list append
operator, chances are your solution is running in quadratic time. This function
should behave exactly like the Haskell built-in `reverse :: [a] -> [a]`.

“` {.haskell}
*Main> rev [1,2,3]
[3,2,1]
*Main> rev []
[]
*Main> rev [“hello”, “world”]
[“world”,”hello”]
“`

### app

Write a function `app :: [a] -> [a] -> [a]` which appends two lists. This
function should behave like the Haskell built-in `(++) :: [a] -> [a] -> [a]`,
and should run in linear time (in the size of the first list).

“` {.haskell}
*Main> app [] [1,2,3]
[1,2,3]
*Main> app [1,2,3] []
[1,2,3]
*Main> app [4,5] [1,2,3]
[4,5,1,2,3]
*Main> app [“hello”, “world”] [“and”, “goodbye”]
[“hello”,”world”,”and”,”goodbye”]
*Main> app “hello” “world”
“helloworld”
“`

### inclist

Write a function `inclist :: Num a => [a] -> [a]` which adds `1` to each element
of the input list.

“` {.haskell}
*Main> inclist [1,2,3,4]
[2,3,4,5]
*Main> inclist [-2,4,5,1]
[-1,5,6,2]
*Main> inclist []
[]
*Main> inclist [2.3, 4.5, 7.6]
[3.3,5.5,8.6]
“`

### sumlist

Write a function `sumlist :: Num a => [a] -> a` which adds all the elements of
the input list.

“` {.haskell}
*Main> sumlist []
0
*Main> sumlist [1,2,3]
6
*Main> sumlist [-3,2,5]
4
*Main> sumlist [3.3,2.8,-1.2]
4.8999999999999995
“`

### myzip

Write a function `myzip :: [a] -> [b] -> [(a,b)]` which zips up the elements of
two lists. The resulting list should be the same length as the shorter of the
two input lists.

“` {.haskell}
*Main> myzip [1,2,3] []
[]
*Main> myzip [] [1,2,3]
[]
*Main> myzip [1,2,3] [“hello”, “world”]
[(1,”hello”), (2,”world”)]
“`

### addpairs

Write a function `addpairs :: (Num a) => [a] -> [a] -> [a]` which zips up two
lists using the addition operator `(+)`. You *must use* your function `myzip`.

“` {.haskell}
*Main> addpairs [1,2,3] []
[]
*Main> addpairs [1,2,3] [4,5,6]
[5,7,9]
*Main> addpairs [1.2,3.4] [-1.2,8.9,7.6]
[0.0,12.3]
“`

### ones

Write a (constant) function `ones :: [Integer]` which produces an infinite list
of the integer `1`.

“` {.haskell}
*Main> take 15 ones
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
*Main> take 0 ones
[]
“`

### nats

Write a (constant) function `nats :: [Integer]` which produces an infinite list
of all the natural numbers starting at `0`. It is OK for this function if you do
not use recursion.

“` {.haskell}
*Main> take 15 nats
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
*Main> take 0 nats
[]
“`

### fib

Write a (constant) function `fib :: [Integer]` which produces an infinite list
of the Fibonacci series starting with numbers `0` and `1`. You can (and should)
use your `addpairs` function here. This is the one place in the assignment where
it really makes sense to use `tail :: [a] -> [a]`.

“` {.haskell}
*Main> take 10 fib
[0,1,1,2,3,5,8,13,21,34]
*Main> take 0 fib
[]
“`

Set Theory
———-

We will be using Haskell lists to represent abstract mathematical sets. Recall
that in a set there are no duplicates, which means that our lists should have
that property as well. To make this simpler, we’ll be storing *sorted* lists,
which means we can only create sets of things that are orderable (notice the
`Ord a` *type constraint* in the type declarations).

As long as your set-theoretic interface functions do not create duplicates and
always return sorted lists, then all of our nice set-theoretic properties will
hold. You may assume that the input sets (Haskell lists) to these functions will
also be sorted and will not contain duplicates.

You may use higher-order functions or recursion as you see fit in this section.

### add

Write a function `add :: Ord a => a -> [a] -> [a]` which will add an element to
the set. Remember that it must add it in the correct place to ensure that the
list remains sorted. It should run in linear time (in the size of the list).

“` {.haskell}
*Main> add 3 []
[3]
*Main> add 3 [1,2]
[1,2,3]
*Main> add 3 [1,3,5]
[1,3,5]
*Main> add 3 [1,5,8,9]
[1,3,5,8,9]
*Main> add “hello” [“goodbye”, “world”]
[“goodbye”, “hello”, “world”]
“`

### union

Write a function `union :: Ord a => [a] -> [a] -> [a]` which unions two input
sets (Haskell lists). This should look similar to the “merge” step of
merge-sort, and should run in linear time (in the added sizes of the input
sets). You *may* use the `add` function defined above, but if you do it probably
will *not* run in linear time, so it’s probably better not to.

“` {.haskell}
*Main> union [] []
[]
*Main> union [1,2,3] []
[1,2,3]
*Main> union [] [1,2,3]
[1,2,3]
*Main> union [1,2,3] [1,2,3]
[1,2,3]
*Main> union [“goodbye”, “world”] [“humans”, “smell”]
[“goodbye”, “humans”, “smell”, “world”]
“`

### intersect

Write a function `intersect :: Ord a => [a] -> [a] -> [a]` which intersects two
input sets. This should run in linear time (in the size of the input sets).

“` {.haskell}
*Main> intersect [] [1,2,3]
[]
*Main> intersect [1,2,3] []
[]
*Main> intersect [1,2,3] [3,4,45,89]
[3]
*Main> intersect [“cruel”, “hello”, “world”] [“good”, “hello”, “world”]
[“hello”, “world”]
“`

### powerset

Write a function `powerset :: Ord a => [a] -> [[a]]` which calculates the
powerset of the input set. Because the output is also a set, it *must preserve
our set properties*, including that there are no duplicate elements and the
elements are lexicographically sorted. Using the functions `union` and `add`
that you’ve already defined is useful here.

“` {.haskell}
*Main> powerset []
[[]]
*Main> powerset [1,2]
[[],[1],[1,2],[2]]
*Main> powerset [“goodbye”, “hello”, “world”]
[ [], [“goodbye”], [“goodbye”, “hello”], [“goodbye”, “hello”, “world”]
, [“goodbye”, “world”], [“hello”], [“hello”, “world”], [“world”]
]
“`

Higher Order Functions
———————-

For these problems, you *must* use higher-order functions. No recursion allowed!

### inclist’

Write a function `inclist’ :: Num a => [a] -> [a]` which increments each element
of an input list.

“` {.haskell}
*Main> inclist’ [1,2,3,4]
[2,3,4,5]
*Main> inclist’ [-2,4,5,1]
[-1,5,6,2]
*Main> inclist’ []
[]
*Main> inclist’ [2.3, 4.5, 7.6]
[3.3,5.5,8.6]
“`

### sumlist’

Write a function `sumlist’ :: (Num a) => [a] -> a` which adds all the elements
of the input list.

“` {.haskell}
*Main> sumlist’ []
0
*Main> sumlist’ [1,2,3]
6
*Main> sumlist’ [-3,2,5]
4
*Main> sumlist’ [3.3,2.8,-1.2]
4.8999999999999995
“`

Part 2: Algebraic Data Types
============================
> _include your definitions for the following problems in `mp1b-adts/src/Lib.hs`_

Algebraic Data Types
——————–

If you haven’t already you may want to read Chapter 8 of Learn you a Haskell,
[Making Our Own Types and
Typeclasses](http://learnyouahaskell.com/making-our-own-types-and-typeclasses).
If Chapter 8 is a little bit over your head, try out Chapter 3 [Types and
Typeclasses](http://learnyouahaskell.com/types-and-typeclasses) first.

We won’t be making any Typeclasses in this MP, but we will be using and making
Types. Specifically, we’ll be making Algebraic Data Types, because that’s what
Haskell supports. Below are two Algebraic Data Types we supply for you to use in
this assignment.

“` {.haskell}
data List a = Cons a (List a)
| Nil
deriving (Show, Eq)

data Exp = IntExp Integer
| PlusExp [Exp]
| MultExp [Exp]
deriving (Show, Eq)
“`

The above code declares two new *type constructors*, `List` and `Exp`. `List` is
a type constructor that takes one type as an argument (for example, if you said
`List Int` that would be a different type than `List String` or `List Double`).
`Exp` takes no type arguments.

It also declares several *data constructors*, two for `List a` and three for
`Exp`. Their types are given below:

“` {.haskell}
— List data constructors
Cons :: a -> List a -> List a
Nil :: List a

— Exp data constructors
IntExp :: Integer -> Exp
PlusExp :: [Exp] -> Exp
MultExp :: [Exp] -> Exp
“`

Notice how the above type declarations are written as if the data constructors
are *functions*. In fact, you can think of them as functions! `Cons` is a
function that takes two arguments, (an `a` and a `List a`) and constructs a
`List a`. If that doesn’t make sense to you, perhaps you need to read Chapters 3
and 8 of Learn You a Haskell as mentioned above.

The nice thing about algebraic data-constructors is that we can *pattern match*
on them (see Chapter 4 of Learn You a Haskell for pattern matching). Suppose we
wanted to make a function `double :: List Int -> List Int` which doubles each
element of the input list. We can just ask ourselves “what should we do for each
of the ways that a `List Int` can be constructed?”

“` {.haskell}
double :: List Int -> List Int
double Nil = Nil
double (Cons i l) = Cons (2*i) (double l)
“`

Notice here that I’ve told Haskell “if the list is constructed as a `Nil`, then
just produce `Nil` again” (this is the base-case). I’ve also told Haskell, “if
the list is constructed as a `Cons i l` (where `i :: Int`, `l :: List Int`),
then multiply the `i` by `2` and `double` the rest of the list” (the recursive
case). Because I’ve *exhausted* all of the data-constructors for `List Int`, I
*know* that Haskell will be able to apply `double` to any `List Int`. If you get
a “non-exhaustive patterns” error, it means you haven’t told Haskell how to
handle all of the ways something of your input type can be constructed.

You’ll be writing a few functions which manipulate the ADTs given above.

### list2cons

Write a function `list2cons :: [a] -> List a` which converts a Haskell list into
our `List` type. Do this recursively (not using higher-order functions).

“` {.haskell}
*Main> list2cons []
Nil
*Main> list2cons [3,2,5]
Cons 3 (Cons 2 (Cons 5 Nil))
*Main> list2cons [“hello”, “world”]
Cons “hello” (Cons “world” Nil)
*Main> list2cons “hello”
Cons ‘h’ (Cons ‘e’ (Cons ‘l’ (Cons ‘l’ (Cons ‘o’ Nil))))
“`

### cons2list

Write a function `cons2list :: List a -> [a]` which converts our `List` type
into the Haskell list type. Do this recursively.

“` {.haskell}
*Main> cons2list Nil
[]
*Main> cons2list (Cons 3 (Cons 4 Nil))
[3,4]
*Main> cons2list (Cons “goodbye” (Cons “world” Nil))
[“goodbye”, “world”]
“`

### eval

Write a function `eval :: Exp -> Integer` which evaluates the integer expression
represented by its input. You may use recursion and higher-order functions.

“` {.haskell}
*Main> eval (IntExp 3)
3
*Main> eval (PlusExp [])
0
*Main> eval (MultExp [])
1
*Main> eval (PlusExp [MultExp [IntExp 3, IntExp 5], PlusExp [IntExp 3], IntExp 5])
23
*Main> eval (MultExp [IntExp 3, IntExp 45, IntExp (-2), PlusExp [IntExp 2, IntExp 5]])
-1890
“`

### list2cons’

Write a function `list2cons’ :: [a] -> List a` which converts a Haskell list
into our `List` type. You are required to use higher-order functions for this,
*no recursion*.

“` {.haskell}
*Main> list2cons’ []
Nil
*Main> list2cons’ [3,2,5]
Cons 3 (Cons 2 (Cons 5 Nil))
*Main> list2cons’ [“hello”, “world”]
Cons “hello” (Cons “world” Nil)
*Main> list2cons’ “hello”
Cons ‘h’ (Cons ‘e’ (Cons ‘l’ (Cons ‘l’ (Cons ‘o’ Nil))))
“`

### BinTree

Write an ADT `BinTree a` which represents a binary tree that stores things of
type `a` at internal nodes, and stores nothing at the leaves.

You must add `deriving (Show)` to the `data` declaration so that GHCi can print
your datatype (as we’ve done above for `List a` and `Exp`). Not doing so will
result in a loss of points.

The data-constructors must have the following types (and names):

“` {.haskell}
Node :: a -> BinTree a -> BinTree a -> BinTree a
Leaf :: BinTree a
“`

### sumTree

Write a function `sumTree :: Num a => BinTree a -> a` which takes a `BinTree a`
(where `a` is a `Num`) from the previous problem and sums all the elements of
its nodes.

“` {.haskell}
*Main> sumTree Leaf
0
*Main> sumTree (Node 3 Leaf (Node 5 (Node 8 Leaf Leaf) Leaf))
16
*Main> sumTree (Node (-4) Leaf Leaf)
-4
“`

### SimpVal

Write an ADT `SimpVal` which represents the values that a simple programming
language can have. We’ll have `IntVal` for integers, `BoolVal` for booleans,
`StrVal` for strings, and `ExnVal` for exceptions.

You must add `deriving (Show)` to the `data` declaration so that GHCi can print
your datatype (as we’ve done above for `List a` and `Exp`). Not doing so will
result in a loss of points.

The data-constructors must have the following types (and names):

“` {.haskell}
IntVal :: Integer -> SimpVal
BoolVal :: Bool -> SimpVal
StrVal :: String -> SimpVal
ExnVal :: String -> SimpVal
“`

### liftIntOp

Write a function\
`liftIntOp :: (Integer -> Integer -> Integer) -> SimpVal -> SimpVal -> SimpVal`\
which will take an operator over integers (like
`(+) :: Integer -> Integer -> Integer`) and turn it into an operator over
`SimpVal`s. If the inputs are not `IntVal`, raise an exception by returning
`ExnVal “not an IntVal!”`.

“` {.haskell}
*Main> liftIntOp (+) (IntVal 3) (IntVal 4)
IntVal 7
*Main> liftIntOp (*) (IntVal 2) (IntVal (-5))
IntVal (-10)
*Main> liftIntOp (+) (BoolVal True) (IntVal 3)
ExnVal “not an IntVal!”
*Main> liftIntOp (+) (IntVal 5) (StrVal “hello”)
ExnVal “not an IntVal!”
*Main> liftIntOp (+) (StrVal “hello”) (ExnVal “not an IntVal!”)
ExnVal “not an IntVal!”
“`

Scroll to Top