Session 24
Programs with State
An Exercise with "O"
Suppose that we know how to multiply and divide whole numbers only by 2, but not by any other number. That's not all that crazy... In hardware, multiplying and dividing by two are primitive shift operations; all other multiplications and divisions require significant work.
If we have two arbitrary integers, m and n, and one of them is a power of two, we can multiply them by taking advantage of this simple fact:
m * n = half(m) * double(n)
If we halve-and-double recursively, eventually m becomes 1 and n becomes the product we seek.
(multiply m n)
that
multiplies in this way, displaying its arguments as it goes
along.
Assume that m
is a power of 2. For example:
> (multiply 16 43) 16 43 ; printed by the function 8 86 4 172 2 344 1 688 688 ; the value of the call
You may find useful the Racket primitives display
,
newline
, and begin
from your
the reading
for today, or even the displayln
I implemented there.
Solutions to the Exercise
To ensure that I only halved and doubled my arguments, I wrote
two quick helpers, half
and double
:
(define (half n) (/ n 2)) (define (double n) (* n 2))
With them in hand, I wrote this solution:
(define (multiply m n) (display-mn m n) (if (= m 1) n (multiply (half m) (double n))))
The display-mn
function uses display
and
newline
to display m and n,
separated by a space.
Notice that I didn't have to use the begin
special
form, because lambda
allows multiple expressions
implicitly. You also saw that in your reading for today.
There are at least two ways I can improve this solution, one substantive and one cosmetic:
- First, the substantive: This function assumes that m is always a power of 2. I would like to handle the general case where m is odd or becomes odd along the way.
- Second, the cosmetic: I'm fussy about unaligned output. I like to mind my p's and q's — and my m's and n's!
Removing the Constraint on m
We can handle odd values for m with one little trick: Use integer division, so that m always remains an integer.
Racket's quotient
function does integer division
for us.
This solution complicates matters, though. Consider the example of 5 * 2. When we use integer division, our next multiplication is (5 / 2) * (2 * 2) == 2 * 4. But notice: we lost a 2! Five divided by two gives two, with a remainder of 1.
So let's add a step to our algorithm. Whenever we encounter an odd m, including m = 1, add the value of n to an accumulator variable. Now we stop when m = 0. At that point, the accumulator variable contains the final answer.
Tidying Up the Output
We can create prettier output by using some of Racket's other I/O primitives.
- Racket supports C-style format strings for high-level formatting.
-
Racket has a
toString()
-like function named~a
that provides keyword parameters to specify the string to be produced.
The Result
This version makes both improvements.
(define (multiply m n) (letrec ((loop (lambda (m n acc) (display3 m n acc) (if (zero? m) acc (loop (half m) (double n) (+ acc (if (odd? m) n 0))))))) (loop m n 0)))
It defines (multiply m n)
as an interface
procedure that calls a helper function to do the work. The
helper uses an accumulator variable to sum up all the
n's that it loses when it encounters an odd m.
This is a great place to use a local recursive function with
letrec
.
My new display function uses the Racket primitive function
printf
, which behaves like a Python format string
and looks a lot like a C format statement. It also calls a
helper function named (pad n)
to show each
integer in an equal-sized field. (pad n)
creates
those strings using Racket ~a
and its keyword
arguments, which are similar to what you see in languages like
Python.
Our new solution exhibits some interesting behavior related to powers of 2:
> (multiply 32 473) ; one addition to acc > (multiply 31 473) ; one addition to acc on every pass
This old algorithm is not just an academic exercise. At the machine level, doubling an integer is the same as a shifting the integer left by one bit, and halving is the same as a one-bit right shift. Shift operations are much faster than the more complex multiplication operation, so compilers try to use them whenever they can.
We can demonstrate this by redefining half
and
double
to use Racket's
arithmetic-shift
operator. Between this operator and the use of tail recursion,
our solution is quite efficient!
With I/O, We Uses Sequences of Expressions
The functions we have written today, as well as the functions you saw in the reading for today, are different from all of the functions we have written up to this point.
They have two expressions in their body, which are executed in order.
Our previous functions all had exactly one expression in their bodies, which was returned as the value of the function.
Why haven't we ever used a sequence of expressions before today? Because we have not used any operators with side effects before today!
When a function uses I/O operations to display characters on the screen, it has an effect on the world other than the value it returns to the caller. The screen now looks different.
The same is true is a function reads from a file: it has an effect on the world other than the value it returns to the caller, because now the state of file stream is different.
Sequencing only matters when expressions have side effects.
Unless you are using operations that change the state of the world, you do not need sequences of expressions. When writing programs in a functional style, we had no need for sequencing. Indeed, the presence of a sequence of statements in a purely functional program is a sign of an error.
The converse of that rule is also true:
Side effects only matters when expressions are in sequence.
Without a sequences of expressions being executed in order, there is no next moment in the execution. No one will be able to see the effect!
Most programming languages provide a way for the programmer to tell a program the order in which to execute a set of statements.
In Pascal and its descendants, including Ada, the
begin..end
construct indicates a sequence of
statements to be executed in order. In Java and C, the
{..}
play the same role. You can almost think of
these syntactic structures as high-level operators
that tell a program to execute a sequence of statements
in a particular order.
In Racket, we can introduce order in several ways. The first is
the special form begin
, which you read about
for today:
(begin <expr_1> ... <expr_n>)
The begin
expression accepts a sequence of
expressions and guarantees that Racket will execute them
in order.
Input/output and sequences of statements are topics with which you have much experience, so we do not need to spend a lot of time on them. For that reason, I gave away the secret by asking you to study a short reading on some of Racket's imperative features for today's session.
Displaying values on the screen is only one kind of side effect. Let's consider another, which will open the door to a familiar style of programming.

With Sequences of Expressions, Our Programs Can Have State
The day has finally arrived.
Up to this point, our discussion of data abstractions has had
the same character as the rest of the course. Every data value
has been immutable, that is, unchangeable. Even when we
created local "variables" with let
, we assigned a
value to each variable exactly once: at the time it was
created. The value of the "variable" never varied.
But we know that most languages provide data items that are mutable, whose values can be changed. They really are variable. Indeed, the programming styles with which you are most familiar — procedural and object-oriented — are based in large part on the idea of mutable data. How is this idea implemented in a programming language?
At times, some of you have been frustrated with Racket because we were not using several features that you rely on in writing programs in other languages: statement sequencing, I/O operations, and variables. These features all belong to a style of writing programs called imperative programming.
This name comes from the idea that programs using such features treat the computer as a data storehouse. The program's job is to tell the computer what to do, to go through a sequence of state changes. In this style, the primary purpose of a program expression is not its value but some side effect.
Obviously, there are some situations in which writing programs in a purely functional style is awkward, or even seemingly impossible. Even Racket's minimalist ancestor Scheme provides a few essential imperative features: procedures and special forms whose purposes are not to compute values but rather to generate side effects. These include I/O operations that read and write data from standard input and from files. Racket I/O is based on the the idea of a port, which behaves much like a stream in Java or C++.
Racket provides many more I/O options, including C-style format
strings. I used printf
in
my second solution
to the Russian peasant multiplication exercise.
Some programming languages allow only a functional style; they provide no imperative operators at all. Perhaps the best example of a pure functional language these days is Haskell. Such languages do allow input/output operations, but only through operators that compute values and preserve the notions of function and value. (If only we had more time.... ™)
We now turn our attention to the idea of mutable data in Racket and in programming languages more generally.
In particular, we will consider how to use mutable data to implement data abstractions that can hide imperative details from the client code. As we do, we will continue to consider the implications of these features for language interpreters, especially how they might represent such data and how they might interpret imperative expressions.
Changing the Value of a Data Object: withdraw
Suppose that we wanted to build a model of a bank account. We would like to be able to withdraw funds from the account, so we would include withdrawal as one of the operations on our bank account ADT. We might try something like this:
(define balance 100) (define withdraw (lambda (amount) (if (>= balance amount) ;; balance is a free variable!! (begin (define balance (- balance amount)) balance) (error "Insufficient funds" balance))))
But Dr. Racket screams at us:
define: not allowed in an expression context in: (define balance (- balance amount))
What about internal define
s?
Some of you noticed in your readings at the start of the course
that Racket allows define
expressions at the top
level of a lambda
body. When used that way, they
behave roughly like a letrec
. At the time, you did
not have a deep enough knowledge of Racket to understand when you
could use an internal define
and when you couldn't,
so I outlawed it. Also, we were trying to learn functional
programming style, and internal define
s are a way to
sneak imperative thinking into our code!
When the interpreter encounters a define
expression
inside the definition of withdraw
, it faces a couple
of tough decisions. Are we creating a new name, or are we
changing the value of an existing name? If we are creating a new
name, do we intend it to be a globally-accessible name or a
locally-accessible one?
If we intend for the new definition to be global, then we are
overwriting the old value, which in effect makes the definition a
value change. If we intend it as a local definition, then the
effect of withdraw
will not be visible outside of
the function, and the global value of balance
will
remain 100.
Racket avoids the confusion by flagging such a use of
define
as an error.
In many ways, this approach helps us as programmers. By removing
the semantic ambiguity that a nested define
can
cause, Racket simplifies learning the language and using it to
write programs
Racket's approach embodies an important idea:
Defining a name and
changing the value of a named object
are different activities and
ought to be different operations in the language.
Programming languages that use the same name or symbol for two different ideas lead to code that confuses both the writer of the code and the reader.
This is a common source of difficulty for people learning to program in C++. Consider these two cases:
Foo a = new Foo(); || Foo a; || a = new Foo();
Foo a
creates and initializes an object.
The =
statement on Line 2 assigns a value
to an object. The example on the right creates two
Foo
s!
Racket was designed to keep separate ideas separate. It includes
a special form named set!
for changing the
value of a data object that has already been defined. Note that
the name of this operator ends with an exclamation mark. This
naming convention tells us that the operator modifies one of its
arguments, usually the first. We have seen and used a similar
convention for naming predicates, whose names end with question
marks.

Operators such as set!
are called mutators,
because they change the value of something. Computer scientists
usually pronounce the exclamation mark as "bang". So, we call
the operation set!
"set bang". (Here is a bit on
origin of this usage.)
As you may have seen already, Racket provides other mutators for
specific data types, such as vector-set!
for changing
a value inside a vector. It even provides set-car!
and set-cdr!
. (!) But don't try them on
regular cons
cells. They operate only on
mutable pairs,
which are created by the function mcons
.
To see how set!
works, let's re-define
withdraw
to work using set!
:
(define balance 100) (define withdraw (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) (error "Insufficient funds" balance))))
This works just fine, and withdraw
now behaves as
we had hoped. The only problem with this definition is that the
balance is external to the definition of withdraw
,
which means that other functions can also modify its
value...
> (withdraw 20) ;; I need a little cash.
80
> balance
80
> (withdraw 40)
40
> (embezzle 30) ;; Hey! What's going on here??
> (withdraw 40) ;; I need a little more cash, but...
Insufficient funds: 10
We know that letting an ADT reveal its implementation can have bad side effects (see our discussion from last time), but this is the worst possible way. How can we prevent all access to the balance of a bank account from any other function in the world?
Using Closures to Encapsulate Data Objects
We could try to solve this problem by making balance
internal to the withdraw
function using a
let
expression:
(define withdraw (lambda (amount) (let ((balance 100)) (if (>= balance amount) ;; now balance is bound (begin (set! balance (- balance amount)) balance) (error "Insufficient funds" balance))))) > (withdraw 20) 80 > (embezzle 50) ;; Whatever it does ... ... > (withdraw 20) ;; ... it doesn't touch my balance! 80 ;; But neither does another call to withdraw. > (withdraw 60) ;; withdraw always starts at $100. 40
Whoa! By treating balance
as a local variable in
this way, each call to withdraw
begins with a
balance of $100. That may make the bank's clients happy, but
probably not the bank!
Using let
inside the lambda
expression,
we have clobbered the idea that the account balance persists over
time. Each call creates a new local variable, with the same
initial balance. One way to solve this problem is to create the
balance
outside of the withdraw
function itself by making the let
the body of the
define
:
(define withdraw
(let ((balance 100))
(lambda (amount)
(if (>= balance amount) ;; balance is bound
(begin
(set! balance (- balance amount))
balance)
(error "Insufficient funds" balance)))))
> (withdraw 20)
80
> (withdraw 100)
Insufficient funds: 80
This works, but why? Because the lambda
expression
that is withdraw
was created in an
environment in which balance
exists and has
an initial value of 100. Each call to the function named
withdraw
refers to the existing balance
object, whatever its current value. But withdraw
does not create balance
, so no local
definition shadows the (relatively) global one.
The idea underlying this technique is called a closure.
We write a function (here, withdraw
) that refers
to a free variable that wasn't free at the time the
lambda
was created. The function carries with it
a reference to the data items that exist when it is created, so
every evaluation of the function refers to this set of
variable/value bindings.
The set of variable/value bindings is called an environment.
By carrying its environment with it, the function bound to
withdraw
can access and modify the same data object
over an extended number of uses. The closure allows the data to
persist over time, as long as the function that refers to it
exists.
Supporting More Than One Customer
We are getting close to a satisfactory solution. One last problem remains. Our definition "hard-wires" the initial bank account balance as 100. We probably do not want this to be the case, because different customers will want to open accounts with different initial balances. So we re-write our definition one more time, creating a constructor for bank accounts that support withdrawals:
(define make-withdraw (lambda (balance) (lambda (amount) (if (>= balance amount) ;; balance is still bound, but (begin ;; to a new object on each call! (set! balance (- balance amount)) balance) (error "Insufficient funds" balance)))))
We call our new function make-withdraw
, and it takes
a single argument, the initial account balance. Evaluating
(make-withdraw 60)
returns a withdrawal function that
works on a new bank account balance with an initial value
of 60. Each call to make-withdraw
creates a new
formal parameter and produces a new function that is, in effect,
a different bank account.
We might use make-withdraw
in this way:
> (define account-for-eugene (make-withdraw 100)) ;;; eugene is poor
> (account-for-eugene 20) ;;; withdraw $20
80
> (define account-for-bill (make-withdraw 400000000)) ;;; bill is rich
> (account-for-bill 20) ;;; withdraw $20
399999980
> (account-for-eugene 20) ;;; withdraw $20
60
> (account-for-eugene 120) ;;; withdraw $120
Insufficient funds: 60
> (account-for-bill 120) ;;; withdraw $120
399999860
This way of using of a closure gives us more flexibility. We
write a function (here, make-withdraw
) that receives
an argument (here, the initial balance). The formal parameter of
make-withdraw
creates a new balance
object, which is referred to by the free variable in the
lambda
that is returned as
make-withdraw
's answer. This allows
make-withdraw
to create distinct functions
(for example, account-for-eugene
and
account-for-bill
) that modify unique data objects.
In effect, make-withdraw
acts like a
constructor for a new object. We can also say that
make-withdraw
is a factory method.
Actually... We have already used the idea of a factory method to create a closure this semester, albeit without mutable data. Think back to when we first learned about higher-order functions in Session 5. In your reading assignment that day, I created a generator of verification functions for self-verifying numbers:
(define make-validator (lambda (f m) (lambda (list-of-digits) (zero? (modulo (apply + (counted-map f list-of-digits)) m)))))
make-validator
is a factory method that creates
validator functions. It creates a function in an environment
where f
and m
are bound, and then returns
the function as its answer. It never changes
f
or m
, but it does continue to use them.
Then, consider our examples of curried functions in the reading for Session 6. They were based on the idea of creating a new function that has access to the original argument:
(define curried-add (lambda (x) (lambda (y) (+ x y))))
In fact, we used a closure every time we created a function that returned another function. Sometimes ideas aren't so scary if we treat them as matter of fact!
Variable Assignment and Sharing
All of the programs we have written thus far have had the form of a "black box" with inputs and a single 'output' value. The value of the output depended only on the values of the inputs passed to the function, not on any previous value. These programs employed no notion of state at all.
That is what makes it functional programming. All interactions between functions occur by passing arguments. This makes it easier to write, reason about, and modify individual pieces of code. For those of you experienced in imperative programming, though, functional programming seems to make it more difficult to write whole programs, at least until you become comfortable with the new way of thinking.
To this point in the course, we have thought of a name being associated with a value. For some applications, though, we naturally think of a data object changing values over time. To think about a variable object, we need to think of a name being associated with a location that holds a value. We can't change the link from the variable's name to the location, but we can change what is stored there.
Your reading for next time will explore the relationship among name, value, and location a bit more.
Let's consider how Racket implements mutable data, both as an example of what is essential and with an eye toward how we can implement mutable data in a language interpreter.
Wrap Up
-
Reading
- Review these lecture notes, especially the last two sections, Changing the Value of a Data Object and Using Closures to Encapsulate Data Objects. Work through the code in today's zip file.
- Prepare for next time by reading a short section on denoted and expressed values.
-
Homework
- Homework 9 is available and due on Monday. Homework 10 will become available the next day. Homework 9 is the beginning of a three-part project, so get a good start on it now!