Skip to content

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 ghci prompt.

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.