Session 25
Implementing Objects with Closures
Download this zip file to use as you work through Session 25.
A Quick Review
A Quick Warm-Up: Count On Me
We now know how to write functions that remember things.
counter
that gives us
the next integer every time it is called.
For example:
> (counter) 1 > (counter) 2 > (counter) 3 > (for-each (lambda (n) (counter)) (range 10000)) > (counter) 10004Challenge 1: Modify your function so that we can create and use multiple counters at the same time.
Challenge 2: Modify your function so that its counter wraps around to 0 whenever it reaches an upper limit. The user provides the upper limit at creation time.
Some Counter Solutions
Here are
possible solutions
for all three versions of the exercise. The function
counter
is a simple example of
a closure,
a function that maintains state. We learned about this idea at the
end of our last session.
-
We can create a counter with memory simply by returning
a
lambda
from inside alet
. -
To enable the use of multiple counters at the same time,
we return
a
lambda
from within anotherlambda
, creating a counter "factory". -
To create bounded counters, we have the outer
lambda
take an argument and add a little logic to theadd1
step so that the function knows when to wrap around.
Note: I used for-each
in my demo code above.
(for-each proc lst)
is a built-in Racket
procedure that applies proc
to every item in
lst
, in sequence, like map
. However,
for-each
does this for the side effects and returns
nothing. It is similar to
the homegrown for-each
procedure
you saw in your reading a couple of sessions back.
"You Are Here"

We are now studying how programs can have state, values
that change over time. Last time,
we introduced the idea of mutable data and learned about
Racket's set!
primitive, which modifies the value of
an existing object. In reading for today, you learned about the
distinction between
denoted and expressed values,
which connects how programs treats names with the underlying
machine model. Here is a simple glossary of terms:
- An identifier is a name used in the code.
- A binding is a connection to an actual value or function.
- A variable is an identifier plus a binding.
With mutable data, a program can represent values that change over time. However, it needs a way to remember data that is not recreated every time the program is executed. This is the idea of a closure, a function that is created in a local context where an identifier has a binding. The function is able to access the variable even after it is seemingly out of scope, because the closure stores both the function and the bindings that existed at the time the function was created.

In the case of counter
, the package consists of the
lambda
expression and the binding of n
to its location.
Now we can see why the region of an identifier is not the same as its scope. In a closure, the region of an identifier is the body of the procedure. However, a closure outlives the local identifier, and the scope of the variable is the lifetime of the closure!
A closure creates a hole in space where interpretation is different from the space that surrounds it. We see this idea in the real world, too. For example:
The American embassy in Paris occupies a very nice building on the Place de la Concorde. Certainly, the embassy is physically within the boundaries of France. But when you step inside the embassy, what country are you in? You're no longer in France. You're in the United States, and US law applies.
In a similar way, a closure behaves like a sovereign state. Though the code travels to other locations in the program, the identifiers in that code retain the meaning they had in the code where they were created.
Consider our make-counter
function. The region of
the variable n
is the body of the let
expression that declares it. But then we call
make-counter
:
(let ((n 42)) (let ((clock-tick (make-counter))) (clock-tick)))
When clock-tick
set!s an n
, it
is the object inside its closure, not the one that exists when
clock-tick
is used. The n
created by
the let
expression is still alive and able
to be seen and changed. Its scope is the body of the
let
expression that creates it.
Changing the value of an object becomes meaningful only now that the object can exist over the course of multiple invocations of the procedure. Procedures that change the value of an object are called mutators, an addition to our vocabulary that refers to any procedure or special form that treats a data objects as variables for the purpose of changing the values, which are stored in particular locations.
Today, we continue with our discussion of programs that have state, including procedures that share data. By the end of the session, you will see how we can use closures of shared variables to implement many of the familiar concepts from object-oriented programming.
Toward Object-Oriented Programming
As we introduced mutation, sequences, side effects, and closures over the last two sessions, we began to move from the realm of functional programming into the realm of imperative programming. With the idea of a selector procedure, we have begun to move toward a particular imperative form: object-oriented programming. While we can write OO programs in a functional style, with no side effects, one of the powerful features of OOP is the ability to create objects that manage their own state.
The closure returned by make-account
behaves like
a simple object:
-
We can send it a
deposit
message or awithdraw
message. - In response to such a message, the account object selects the appropriate member function to perform and returns it as an answer.
- The message sender can then invoke the procedure with the necessary arguments.
The syntax we use to send messages to an object implemented as a selector function is not as convenient as we might like:
> ((account-for-eugene 'withdraw) 10) 90
What can we do to improve that?
A Message Passing Syntax for Our Objects
The syntax for sending messages to our objects looks very
Racket-y. It reflects the fact that a bank account is
a procedure and that, when we send it a withdraw
or
deposit
message, the account returns a
procedure that we must call. Can you think of a more convenient
syntax, one that doesn't expose these implementation details?
How might we implement it?
Here is a possible solution:
(define send ; or even "←"
(lambda (object message . args)
(apply (look-up-method object message) args)))
(define look-up-method
(lambda (object selector)
(object selector)))
Now we can interact with our objects using a more convenient syntax, and with fewer parentheses:
> (send account-for-eugene 'withdraw 10) 160
Why do you think I defined a look-up-method
function?
Think back to
the apply-ff
function
we added to the finite function ADT...
Without the look-up-method
function, send
must assume that objects are implemented as functions. However,
we know that there is
an infinite variety of implementations
for any data abstraction. We are usually better off building
tools that do not assume a particular implementation.
Racket Reminder: Recall that the .
in
send
's parameter list works just like the dot
in dotted pair notation. The first argument is bound to
object
. The second is bound to message
.
The rest of the arguments are bound as a list to the parameter
args
, no matter how many are passed.
Exercise: An Alternate Implementation
Implementing a bank account as a function brings to mind an idea we encountered previously in our discussion of finite functions: the use of a Racket function as the concrete implementation of a datatype. As with previous ADTs, we have many alternative implementations available for implementing our little objects. Perhaps a data-based solution would be simpler?
make-account
that returns a
list of procedures that share the balance variable.
For example:
> (define eugene (make-account 100)) > (list? eugene) #tHow will we withdraw and deposit money from the account now? Try it out.
Here is
a sample solution.
If we'd like, we can define functions named withdraw
and deposit
that access the desired function in the
list and calls it.
That is nice, but now the syntax for accessing the object and its methods has changed. That's a sign of that we have not designed a solid abstract interface for bank accounts. Different implementations of an ADT should never affect client code!
However, we did create
syntactic sugar
to hide such details from our procedure-based implementation.
Can we adapt that idea here? You bet we can: scroll down in
the code file for a new version of send
.
I noted last week that I think this idea of data-as-function is very cool, and closures show another reason why. Always keep in mind that data can be implemented as functions, and functions can be implemented as data. This sort of flexibility gives you more options when solving problems, including some the problems we face when writing a language interpreter.
Closures as Objects in Object-Oriented Programming
Let's return to our bank account, implemented as
a different kind of message selector
and extended with a balance
operation:
(define make-account ;; Creates a procedure that creates a (lambda (balance) ;; closure around balance. All of the (let ((withdraw ;; procedures share the balance variable. (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) (error "Insufficient funds" balance)))) (deposit (lambda (amount) (set! balance (+ balance amount)) balance)) (get-balance (lambda () balance)) (error (lambda args (error "Unknown request -- ACCOUNT")))) (lambda (transaction) (case transaction ('withdraw withdraw) ('deposit deposit) ('balance get-balance) (else error))))))
This one function implements many of object-oriented programming's basic ideas:
-
make-account
is a constructor that returns a new instance of a bank account. - This object responds to deposit, withdraw, and balance messages by performing methods with similar names. It ignores all other messages.
- The object encapsulates its instance variable, which is accessible only to this instance.
-
Each new account has its own identity and its own
copy of
balance
.
What else do we need to do object-oriented programming? We might
want a cleaner message-passing syntax. Our send
function moves us in that direction. But the cleaner syntax is
really just sugar.
One way to make the object more convenient to use is to convert it into a message responder rather than a message selector:
> (define ellen (make-account 100)) > (ellen 'withdraw 100) 0 > (ellen 'deposit 50) 50 > (ellen 'balance) 50 > (ellen 'deposit 20) 70
Quick Exercise: How could we modify send
to
work with this kind of object?
Going Deeper
There are other semantic features of OOP that we might want to have:
- inheritance, the ability to create sub-classes that inherit behavior and state from an existing class
- dynamic polymorphism, in which two objects that are instances of the same different classes can be used by the same client code because they implement a common interface
Dynamic Polymorphism
What, if anything, do we have to do to implement dynamic
polymorphism using our closure-based objects? Racket is
dynamically typed, so we already have the ability to treat any
object like some other. Any selector procedure that accepts
withdraw
and deposit
messages can act
as a bank account. Client code wouldn't know the difference.
This means that dynamic polymorphism is free in our
implementation of OOP.
If we create a class-based implementation of OOP that checks types, we will need to implement a mechanism to allow polymorphic objects.
Classes and Inheritance
Inheritance is a challenge for this style of implementing OOP, because it does not explicitly represent the idea of a class.
If your only exposure to objects is through Python or Java, you may be surprised to learn that not all object-oriented languages have classes! Consider Self, a language designed at Sun Microsystems (now owned by Oracle) beginning in the mid-1980s. In Self, there are no classes, only objects. You create a new class by "cloning" another object, called a prototype. You create a new kind of object by adding new state and behavior to an existing object. Self is quite cool and has influenced many languages since. You can do some things must more simply and elegantly in Self than in a class-based OO language.
The most popular and influential prototype-based language these days is Javascript. It is a language with broad application and remains a hotbed of development, thanks to its ubiquity on the web.
In a dynamically-typed language such as Racket, classes might play a less important role than in a language such as Java. But how might we implement them?
If we think about classes in a different way, we can implement something simple that captures the idea. What if we think of a class as an object that can create instances for us? In that sense, we already know how to implement a class: use the same sort of closure that we use to implement objects!
If you'd like to see this idea in action, take a look at
this Racket file,
which defines make-account
as a function that
responds to the message new
. In response, it
returns a constructor for a bank account. It even has class
variables (often called 'static variables' in Java and C++)
that we can interact with through the class. Here is a sample
interaction:
> (define eugene ((make-account 'new) 100)) 100 > ((eugene 'balance)) 100 > ((eugene 'deposit) 100) 200 > ((eugene 'balance)) 200 > ((bank-account 'count)) 1 > (define mary ((make-account 'new) 100)) > ((bank-account 'count)) 2
In this approach, a class is a function that returns a new object to us. The new object is... a function.
Of course, this new style of bank account is a closure, too, so
we don't have to use the parenthesized syntax. Our original
version of send
works with them:
> (define eugene (send make-account 'new 100)) > (send eugene 'balance) 100 > (send eugene 'deposit 100) 200 > (send eugene 'withdraw 50) 150 > (define mary (send make-account 'new 1000)) > (define alice (send make-account 'new 10000)) > (define bob (send make-account 'new 100000)) > (send make-account 'count) 4 > (send alice 'balance) 100000
We now have a way to implement multiple constructors, as we see
them in Java and C++. We can add another case to the selector
procedure returned by bank-account
!
In Java, classes aren't objects to which we send messages;
class
is a special construct. But in a language
such as Smalltalk, everything is an object. A Smalltalk class
is an object to which you send a message in order to create an
instance of the class. Today, languages like Ruby provide the
same feature. With one more level of closure wrapping our
object closure, we are able to implement a class with nothing
new. As David Wheeler, a programmer in the early 1950s (!)
once said,
Any problem in computer science can be solved with another layer of indirection.
Programmers of that time were solving many of the problems that cause us to bang our heads against the wall, and they did so at a time when the tools available were a lot less powerful. Often, constraints make us more creative.
Quick Trip Back to letrec
A few weeks back, we learned about
local recursive functions
and the challenge they pose for let
. We then saw
that Racket provides a special form letrec
for
creating local recursive functions. At the time, I
said:
Like thelet
expression,letrec
is a syntactic abstraction. We can implement the equivalent of aletrec
expression in "vanilla" Racket, using (1) a feature of the language we have not studied yet, but which you know well, and (2) a bit of a hack. Can you imagine how?
We have now studied the Racket feature we need, set!
.
We have now seen the hack we need several times in the last two sessions, which is really the idea of a closure.
Take a peek at
this code,
which creates a function that refers to itself without
using letrec
. It shows us a translational
semantics for letrec
.
letrec
really is a syntactic abstraction. It can be
defined as let
+ set!
!
Wrap Up
-
Reading
- Review the notes for this session and the last one, on functions with state. We didn't cover the final section of today's notes, Going Deeper, in class, so read it for a flavor of what is possible.
-
Homework
- This short assignment will help you prepare for our next session, which will be a review of one possible solution for Homework 9. The assignment is worth ten points, half the value of a regular assignment. There are right answers to some of the questions, but they are not the primary goal of the assignment. The primary goal is to study a a Boom interpreter and reflect on the choices you made when implementing your own code.
- Homework 10 will be available next time and due one week from then.
-
Credits
- I found the example using the American embassy in Paris in an early draft of Matthew Butterick's Beautiful Racket, which we first encountered in Session 21 on creating new languages within Racket. The example did not survive into the final version of the book, but I like it and still use it, and Matthew deserves credit. Beautiful Racket is a beautiful book about language-oriented programming in Racket. If you are interested, check it out!