HW 2: FUNctional programming¶
Time to write some real Haskell. This assignment has you implementing functions that build on Module 02.1 and Module 02.2 — recursion, pattern matching, and higher-order functions — and ends with a neat trick: building arithmetic itself (addition, multiplication, exponentiation) out of nothing but a single function and a number.
How you'll get materials and submit this assignment
Getting the starter code and turning in your work follows the same process as every homework this semester — see The Homework Workflow for the full walkthrough. In short: from the hw2 template repo, make your own private copy, clone it to the course server, and submit your test output and code on Gradescope. The rest of this page covers what the assignment actually asks of you.
What You'll Do¶
- Get connected to the course server, where Haskell is set up for you.
- Learn some good Haskell programming practices, including style and testing.
- Practice writing functions in Haskell — especially recursive functions.
- Practice writing and using higher-order functions.
- Use higher-order functions to build complex operations out of simple ones.
Checklist¶
- Get connected to the course server
- Read about good Haskell programming practices
- Learn about modules and tests in Haskell
- Get the materials for the assignment (see The Homework Workflow)
- Implement
fib - Add tests for
intfib - Implement
intfib - Implement
step - Implement
countStep - Implement
countCallsUntil - Implement
addition - Implement
multiplication - Implement
exponentiation - Turn in the assignment (see The Homework Workflow)
Preparing for the Assignment¶
Before diving into the code, take some time to get set up — this is worth doing properly, and we've built time for it into the assignment. Do your work on the course server, where Haskell and the test libraries are already installed. The How-To Guides cover connecting with VS Code, the homework workflow, and how modules and tests work.
For this assignment, we won't grade for code style — but it's still worth practicing. Use descriptive names and helpful comments where they earn their keep.
Materials and Collaboration¶
You'll get the starter files from the hw2 template repo: make your own private copy and clone it to the course server. The Homework Workflow walks through every step. Your job is to fill in the definitions marked undefined in Arithmetic.hs and Functions.hs (plus adding the intfib tests described below); the starter code's names and types are fixed, but you can always add helper functions.
Unlike HW1, you can work with a partner on this assignment — remember to follow the pair-programming rules from the syllabus. If you pair up, add your partner as a collaborator on your repo (also covered in that guide).
Functions¶
In this part, you'll implement several functions emphasizing recursion, ending with a higher-order function. Two things to keep in mind throughout:
- You're not allowed to change the names or types of anything in the provided starter code — but you're always free to add your own helper functions.
- Only one task below requires you to write test cases, but you're encouraged to write as many as you like, for all of your code.
fib (10 points)¶
In Functions.hs, fill in the definition of:
fib :: Integer -> Integer
which computes the Fibonacci number F(n) for n >= 0:
F(0) = 0F(1) = 1F(n) = F(n-1) + F(n-2)whenn >= 2
(This sequence is often named after Fibonacci, though it was studied by earlier mathematicians too, including Pingala.)
A few notes:
- The idiomatic way to write this in Haskell is pattern matching, not
if/then/else. - Go for simplicity over efficiency — an exponential running time is fine here.
- Run the tests to check your work (the provided suite only covers a handful of cases).
Example
ghci> :l Functions.hs
*Functions> fib 3
2.
intfib (20 points)¶
It's occasionally useful to consider a more general specification:
F(0) = 0F(1) = 1F(n) = F(n-1) + F(n-2)for all integersn
This specification turns out to uniquely determine F(n) for every integer n — not just non-negative ones. So now we can talk about negative Fibonacci terms too.
Before coding, work through a few examples by hand, especially for negative terms. What must F(-1) be? What about F(-2)?
Task 1: In test/FunctionSpec.hs, add at least two test cases for intfib — be sure to test negative numbers. (The test syntax looks a little unusual: copy, paste, and modify one of the existing context blocks. Before implementing intfib, run the suite once to confirm it compiles and that all the new tests fail.)
Task 2: In Functions.hs, implement intfib so it computes F(n) for any integer n. Clarity and correctness matter more than efficiency here — conditionals (if/then/else or guards) are fair game.
step (10 points)¶
In Functions.hs, fill in:
step :: Integer -> Integer
which computes:
f(n) = n / 2, whennis evenf(n) = 3n + 1, whennis odd
An if/then/else expression is probably the right tool here. Haskell has built-in even and odd functions, and you'll likely want div for integer division (rather than /, which is fractional).
Example
ghci> :l Functions.hs
*Functions> step 3
10.
*Functions> step 2
1.
countStep (15 points)¶
In Functions.hs, fill in:
countStep :: Integer -> Integer
Given an Integer n, this returns the fewest number of times step must be called, starting from n, to reach 1.
For example:
countStep 1is0— no calls needed, we're already at1.countStep 2is1.countStep 4is2.countStep 3is7.
Recursion is your friend here.
countCallsUntil (20 points)¶
Now generalize countStep to count calls to any function of type Integer -> Integer. Define countCallsUntil, which takes three arguments:
- a function
f :: Integer -> Integer - a stop value,
Integer - a start value,
Integer
The result is the number of calls to f needed to get from start to stop. For example:
countCallsUntil step 1 1is0countCallsUntil step 1 3is7countCallsUntil (+ 1) 10 0is10
Task: In Functions.hs, fill in the definition of countCallsUntil.
Testing Your Functions¶
Run everything on the course server — it already has GHC and the test libraries. From the assignment directory, run the whole suite:
runhaskell -itest test/Spec.hs
The last line is your summary, e.g. 45 examples, 0 failures. Until you've filled in the stubs you'll see lots of failures and Prelude.undefined — that's expected. To run just one file or one group:
runhaskell -itest test/FunctionSpec.hs
runhaskell -itest test/Spec.hs --match "intfib"
Prefer an interactive session? ghci test/FunctionSpec.hs, then type main at the prompt (:r then main after each edit, :quit when done). See Writing and Running Tests for the full picture.
Passing the provided tests doesn't guarantee your code is fully correct — it's worth thinking up a few test cases of your own.
Higher-Order Arithmetic¶
Here's the fun part: define addition, multiplication, and exponentiation using only the number 0, an increment operation, and higher-order functions.
The Raw Materials¶
You're restricted to three building blocks:
zero — just another name for 0:
zero :: Integer
zero = 0
increment — adds one to its argument:
increment :: Integer -> Integer
increment n = n + 1
applyN — a higher-order function taking three arguments: a function f :: Integer -> Integer, a count n :: Integer, and an initialValue :: Integer. It applies f to initialValue, n times. (If n is 0, f isn't applied at all, and the result is just initialValue.) For example:
applyN increment 0 1is1applyN increment 2 1is3
Your Task (50 points)¶
In Arithmetic.hs, fill in addition, multiplication, and exponentiation. You can assume all three are only ever called with non-negative arguments.
Your implementation is severely constrained: you may use only zero, increment, applyN, and your own addition, multiplication, and exponentiation — nothing else. No numeric literals like 1 or 2, and no built-in + or -.
A few hints:
- Think before you code — convince yourself an approach will work before writing any Haskell.
applyNwill show up in each of your three definitions. What function would be a useful argument to it?- Build on what you've already implemented — can
multiplicationbe defined in terms ofaddition?
Example
ghci> :l Arithmetic.hs
*Arithmetic> addition 1 2 -- 3
*Arithmetic> multiplication 2 3 -- 6
*Arithmetic> exponentiation 3 2 -- 9
Testing Your Code¶
Same as before — run the whole suite with runhaskell -itest test/Spec.hs, or just this part with:
runhaskell -itest test/ArithmeticSpec.hs
(or ghci test/ArithmeticSpec.hs then main, if you'd rather stay interactive).
As before: passing the provided tests is a good sign, but not a guarantee. Think up a few of your own.
Turning It In¶
Submit on Gradescope, under HW 02: FUNctional Programming:
- Run the full test suite one last time:
runhaskell -itest test/Spec.hs. - Copy the entire terminal output and paste it into the HW2 text box.
- Upload your
Arithmetic.hsandFunctions.hsto the same assignment (for our records).
The Homework Workflow covers the submission step in context. There's also a short HW 2 reflection to submit separately on Gradescope.
Make sure your final code is committed and pushed to your repo as well.
Where to Go From Here¶
- The
stepfunction is related to the Collatz conjecture (also known by other names) — whethercountStep nterminates for every positive integernis an unsolved problem in mathematics. - Addition, multiplication, and exponentiation are all specific instances of the hyperoperation sequence. Can you define the hyperoperation itself in Haskell, and then define
addition,multiplication, andexponentiationas calls to it?