Lab 3: Tuples, Stacks, and Currying¶
This lab is entirely a ghci session — no files to edit, just you, the interpreter, and a handful of related problems on tuples, list-based stacks, and one of Haskell's more mind-bending features: currying. Each part gives you a few different correct implementations of the same idea and asks what you make of the differences — there's rarely a single "right answer" to those comparisons, we're just after your honest reaction.
How you'll get set up
We're still finalizing the workflow for accessing the programming server for this course. Instructions will be posted here once they're ready — for now, this page covers what the lab actually asks you to do.
Gradescope
As you work, complete the corresponding Lab 03 assignment on Gradescope.
Most questions ask you to paste output or briefly explain what you observed.
Getting Connected¶
- I am able to get to the
ghciprompt.
Gradescope check: ghci prompt
Confirm this on Gradescope.
Functions with Tuples¶
Warmup with pairs. Recall that Haskell pairs (two-element tuples) come with fst and snd to extract the first and second elements:
fst (3, 4) -- 3
snd (3, 4) -- 4
Given this expression, work out what you predict the result to be before you run it:
snd (snd (fst (((1,2),(3,4)),((5,6),(7,8)))))
Then check it in ghci — did you predict correctly?
Gradescope: predicted vs. actual result
Submit your prediction and the actual ghci output.
Swapping. Define a function swap that accepts a pair and swaps the two positions:
swap pair = (snd pair, fst pair)
Try it out on an input pair of your choosing.
Now consider an alternative definition, swap' ("swap prime" — note the trailing ', a common Haskell naming convention for "a variant of"):
swap' (x, y) = (y, x)
Try this one too.
Gradescope: swap vs. swap'
swap and swap' compute the same thing two different ways. What are your thoughts on the two implementations?
Rotating. Write a function leftRotate3 that behaves like this:
Prelude> leftRotate3 (1, 2, 3)
(2, 3, 1)
That is: the element in position 1 moves to position 3, position 2 moves to position 1, and position 3 moves to position 2.
Gradescope: leftRotate3
How would you implement leftRotate3 in ghci?
Stacks via Lists¶
We'll think of a stack as a list of items, [a], where the "top" of the stack is the front of the list.
isEmpty should evaluate to True if the stack is empty:
isEmpty :: [a] -> Bool
Three correct ways to write it:
isEmpty stack = if length stack == 0 then True else False
isEmpty stack = length stack == 0
isEmpty [] = True
isEmpty (x:xs) = False
Gradescope: isEmpty
All three are correct but take different approaches. What are the merits or drawbacks of each?
push takes an item x :: a and a stack [a], and returns a new stack with x added to the top:
push :: a -> [a] -> [a]
Two ways to write it — using the cons operator directly:
push x stack = x : stack
...or observing that push is the cons operator, so just say so:
push = (:)
Gradescope: push
Compare and contrast these two. What do you like or dislike about each? There's no correct answer — just looking for your honest reaction.
pop takes a stack and returns both the popped item and the updated stack:
pop :: [a] -> (a, [a])
Three correct, but different, implementations:
pop stack = (head stack, tail stack)
pop [] = error "cannot pop empty stack!"
pop stack = let popped_element = head stack
rest_of_stack = tail stack
in
(popped_element, rest_of_stack)
pop [] = error "cannot pop empty stack!"
pop (x:xs) = (x, xs)
Gradescope: pop
Same question as above — compare and contrast. What do you like or dislike about each?
Currying and Uncurrying¶
Currying. Define a function f in ghci:
f (x, y) = x + 2 * y
Try f (4, 5) and note the result.
Now create a new function from f using the built-in curry:
g = curry f
Call g 4 5 and note the result. Then check the type of curry itself:
:type curry
Gradescope: what curry does
In light of f (4, 5), g = curry f, g 4 5, and :type curry — explain in your own words what curry does.
One more: define h = curry f 4.
Gradescope: predict h 5
Before running it, what do you expect h 5 to evaluate to? Explain your reasoning, then check it.
Uncurrying. Now go the other direction. Define g in ghci:
g x y = x + 2 * y
Try g 4 5 and note the result. Then create f using uncurry:
f = uncurry g
Call f (4, 5) and note the result, then check the type of uncurry:
:type uncurry
Gradescope: what uncurry does
In light of g 4 5, f = uncurry g, f (4, 5), and :type uncurry — explain in your own words what uncurry does.
Where This Leaves Us¶
Currying is one of those ideas that clicks into place once you've played with it directly rather than just read the definition — the fact that f (x, y) and g x y are two shapes for "the same" function, convertible in either direction, comes up again and again in functional programming.
Gradescope: wrap-up
Leave any comments about the lab in the final Gradescope question, then move on to HW 3.
Next up: HW 3, which builds on tuples, datatypes, and this kind of function manipulation.