Session 25
Implementing Objects with Closures

A Quick Review

... in which I open with a five-minute review of the four "withdraw" functions that close Session 24, using 00-withdraw.rkt as a starting point. We end with a factory method for... an object?

A Quick Warm-Up: Count On Me

We now know how to write functions that remember things.

Write a function named 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)
10004
Challenge 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.

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"

a red arrow labeled YOU ARE HERE pointing down at the caption, which says 'closure'
closure

We are now studying how programs can have state. Last time, we introduced the idea of mutable data and saw Racket's set! primitive for modifying the value of an existing object. You also read 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:

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.

a box labeled 'closure' that contains boxes labeled 'function' and 'environment'
a closure is a package containing a function and an environment

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 our new let expression.

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.

Using Closures to Share Data Objects

At the end of last session, we created a procedure that acts like a constructor for withdrawal procedures:

(define make-withdraw
  (lambda (balance)
    (lambda (amount)
      (if (>= balance amount)        ;; balance is bound, but to
          (begin                     ;; a new object on each call!
            (set! balance (- balance amount))
            balance)
          (error "Insufficient funds" balance)))))

To model a real bank account, we need the ability to do more than just withdraw funds. At the very least, we will want to deposit money into the account, increasing the value of the balance. This requires us to create not just one procedure that can access the value of balance over time, but two. The idea behind the implementation, though, is the same:

create a closure in which a procedure refers to a free local variable

In this case, though, our closure will contain two procedures!

One approach is to use message-passing style, creating a function that receives a symbol as its argument and uses the symbol to choose which procedure to run. You may remember seeing this idea when we looked at a bunch of ways to implement a pair in Session 22.

Consider the following function, make-account, which receives an initial balance as its only argument. It returns a function that responds to withdraw and deposit messages:

(define make-account        ;; Define a function that creates
  (lambda (balance)         ;; a closure around balance.
    ;===========================================================
    (lambda (transaction)        ;; The function uses its arg
      (case transaction          ;; to select an operation to
        ('withdraw               ;; perform on the balance
            ;---------------------------------------------------
            (lambda (amount)      ;; -- returning this procedure
              (if (>= balance amount)
                  (begin
                    (set! balance (- balance amount))
                    balance)
                  (error "Insufficient funds" balance))))
            ;---------------------------------------------------
        ('deposit
            ;---------------------------------------------------
            (lambda (amount)      ;; -- or this procedure
              (set! balance (+ balance amount))
              balance))
            ;---------------------------------------------------
        (else
            (error "Unknown request -- ACCOUNT" transaction))))
    ;===========================================================
    ))

We can now create different accounts with different balances, and make deposits and withdrawals on each.

> (define account-for-eugene (make-account 100))
> ((account-for-eugene 'withdraw) 10)
90
> ((account-for-eugene 'withdraw) 10)
80
> ((account-for-eugene 'deposit) 100)
180
> (define account-for-bill (make-account 10000000))
> ((account-for-bill 'withdraw) 10)
9999990
> ((account-for-eugene 'withdraw) 10)
170

Each call to make-account creates a procedure that has its own local binding for the variable balance. The resulting procedure is a "selector" that returns either the withdraw procedure or the deposit procedure, depending upon the argument it is given. Both of these procedures access the same balance variable. The deposit operation for Eugene's account refers to the balance in its closure, while the deposit operation for Bill's account refers to its own balance. This is a more general use of the closure, in which mutable data are local to a set of procedures.

As we introduced mutation, sequences, side effects, and closures over the last two sessions, we began to move from the realm of functional programming to the realm of imperative programming. With the idea of a selector procedure, we have begun to move toward a particular form of imperative programming: 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:

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 rest of the parameter list is bound to the parameter args, no matter how many arguments 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?

Write a version of make-account that returns a list of procedures that share the balance variable, rather than a 'message selector' function. For example:
> (define eugene (make-account 100))
> (list? eugene)
#t
The code for the withdraw and deposit procedures will be the same, so you don't have to write those lambdas again. Just write WITHDRAW and DEPOSIT in their places.

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 that 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.

Implementing More Object-Oriented Programming

Let's return to our bank account, implemented as a different kind of message selector and extended with a balance method:

(define make-account        ;; Creates a procedure that creates
  (lambda (acct-balance)    ;; a closure around acct-balance.
    (let ((withdraw               ;; These procedures share
            (lambda (amount)      ;; the balance variable.
              (if (>= acct-balance amount)
                  (begin
                    (set! acct-balance (- acct-balance amount))
                    acct-balance)
                  (error "Insufficient funds" acct-balance))))
          (deposit
            (lambda (amount)
              (set! acct-balance (+ acct-balance amount))
              acct-balance))
          (balance
            (lambda ()
              acct-balance))
          (error
            (lambda args
              (error "Unknown request -- ACCOUNT"))))
      (lambda (transaction)   
        (case transaction     
          ('withdraw withdraw)
          ('deposit  deposit)
          ('balance  balance)
          (else      error))))))

This one function implements many of object-oriented programming's basic principles:

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.

We can use our message-sending procedure even more conveniently if we make our object 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 do we modify send to work with this kind of object?

Going Deeper

Note: Read this section for the ideas only. The code gets a bit bigger, and perhaps harder to understand. You don't have to study the code in detail, but please do think about the ideas.

There are other semantic features of OOP that we might want to have:

Classes and Inhertiance

Inheritance is a challenge for this style of implementing OOP, because the idea of a class is not explicitly represented.

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 new 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 new messages by returning 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, bank accounts are closures, 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.

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.

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 the let expression, letrec is a syntactic abstraction. We can implement the equivalent of a letrec 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 several times the hack we need, 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