Higher-Order Programming » Lab Sessions
Session 3
Assistant: Steven Keuchel (steven.keuchel@vub.be)
Section 1: Pairs
The simplest compound datatype in Scheme is a pair of two values. The cons
procedure takes two arguments and creates a new pair, where the first argument
will be stored as the first component of the pair, and the second argument as
the second component.
Pairs and other compound datatypes can be visualized by so-called
box-and-pointer diagrams. For instance the pair a
defined above is
represented by the following diagram:
To access the individual components of the pair, you can use car
and cdr
.
car
will get the value stored at the first component, cdr
the second
component.
The predicate procedure pair?
tests if a given value is a pair.
Exercise 1.1: Swapping
Define a function swap
that takes a pair and produces a new pair, where the
elements are exchanged.
Exercise 1.2: Pair Ordering
Define a function sort-pair
that takes a pair of two numbers and produces a
new pair where the components are ordered, i.e. the left component is smaller or
equal to the right component.
Exercise 1.3: Pair Mapping
Define a function map-pair
that takes three parameters: two functions l
and
r
and a pair x
. map-pair
should apply l
to the left component of x
and
r
to the right component of x
and produce a new pair.
Exercise 1.4: Pair Folding
Define a function fold-pair
that takes two parameters:
- a two-parameter function
c
, and - a pair
x
. Fold-pair should applyc
to the components ofx
.
Exercise 1.5: Folding revisited
Write swap
, sort-pair
and map-pair
in terms of fold-pair
.
Section 2: Trees and lists
Pairs are not limited to primitive datatypes, but can store other compound
datatypes (e.g., other pairs). The following is a diagram of (cons a 5)
.
The following structures are all identical, the first one is the canonical form.
'()
refers to the empty list. You can check for the empty list using (null? p)
.
All these structures have the following box-and-pointer diagram.
The procedure list
builds a list of values.
Exercise 2.1: Simple box-and-pointer diagram
Using pen and paper, draw a box-and-pointer diagram of the following structure.
Exercise 2.2: Advanced box-and-pointer diagrams
Using pen and paper, draw a box-and-pointer diagram of the following structures. Explain the type of each structure (e.g., is it a tree? is it a list?).
Exercise 2.3: Shared memory in box-and-pointer-diagrams
Using pen and paper, draw a single box-and-pointer diagram of all the variables occuring in this program. What is shown when displaying every variable?
Hint! Refer to the report on R5RS to find out what the semantics of cddr
are.
As the box-and-pointer diagrams suggest, compound datatypes in a pair are stored
by reference, not by copy. It is possible to modify the value stored in a
pair via set-car!
and set-cdr!
(for usage, refer to the documentation).
What happens when evaluate (set-cdr! c 'apple)
afterwards? What is changed
when you display every variable again?
Section 3: Higher-Order Procedures on Lists
In this course, we will make use of higher-order procedures, which are procedures that take a first-class procedure as argument.
Exercise 3.1: Sum
In this exercise, we will implement multiple versions of a sum
procedure, that
calculates the sum of all elements in a given list structure.
- Implement
sum
using recursion. - Implement the higher-order procedure
accumulate
that has three parameters: a procedure, a default value, and a list. - Implement
sum
by making use ofaccumulate
For the first part, use the following boilerplate code.
Exercise 3.2: Filtering
Complete the following definition of filter
.
Then, call filter
on the list '(4 8 15 16 23 42)
such that only odd numbers
are kept.
Then, create a predicate that matches positive odd numbers. And pass this
predict to filter the following list: (1 -1 2 -3 5 -8 13 -21 34 -55 89)
.
Hint! As a convention in Scheme, all names for predicate procedures should
end on ?
(e.g., even?
, pair?
,...)
Exercise 3.3: Reversing Lists
The reverse
function takes a list and reverses the order of the elements:
Implement your own version of reverse
. Hint! Write a function with
an iterative process.
Section 4: Higher-Order Procedures on Trees
For the exercises in this section, we will use the following trees.
If you feel stuck on one of these exercises, it can help to draw either a box-and-pointer diagram, or a tree diagram of these structures.
Exercise 4.1: Sum and depth of a tree
First, write a procedure that calculates the sum of all numbers in a tree, and
the depth of a tree. If you are stuck, check the implementation of
count-leaves
given during the theory (Chapter
2, Slide 22).
Predict what will happen if you pass the given trees to your procedures, then apply this procedure on all given trees. Did your outcome match your expectations?
Second, write a generic procedure that processes all elements in a tree. Complete the following boilerplate code. The intuition behind the three parameters is as follows:
combiner
is a two-parameter function that combines two values of the result domain.f
is a one-parameter function that maps a single element to the result domain.neutral
is a value that represents a neutral element of thecombiner
function.
Third, write your procedures of the first part of this exercise using
fold-tree
and verify whether you get the same result.
Exercise 4.2: Mapping the values on a tree.
Write a procedure tree-map
that maps all the values in a tree according to
some mapper function. Can you make use of fold-tree
from the previous exercise
to implement tree-map
? If so, implement this variant.
Bonus Exercises
Bonus Exercise 1: Cycle
Recommended after exercise 3
Write a procedure that takes 3 arguments, and builds a cycle structure. E.g.,
the result of executing (make-cycle 8 5 3)
should look like:
What is the output of when you display a data structure with a cyclic dependency?
Bonus Exercise 2: Generic Function Composition
Recommended after exercise 5
In the previous exercise session you have implemented compose
for function
composition. In the theory you have seen something that should make it possible
to implement compose
such that it can be executed on a dynamic number of
arguments. Improve your implementation of compose
such that it has support for
multiple parameters.
Tip! Check section 4.1.4 of the R5RS manual for the different syntactical
options for <Formals>
.