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.

Write a recursive function (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

Check out this solution, for which I wrote a custom display-mn function. I also wrote half and double functions, to make the code read cleaner and to ensure that I don't use * anywhere in my function.

(define multiply
  (lambda (m n)
    (display-line m n)
    (if (= m 1)
        n
        (multiply (half m) (double n)))))

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:

We can handle odd values for m with one little trick: Use integer division, so that m always remains an integer. Whenever we encounter an odd m, including m = 1, add the value of n to an accumulator variable. Stop when m = 0.

Racket's quotient function does integer division for us.

We can create prettier output by using some of Racket's other I/O primitives.

This implementation makes both improvements.

> (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. To demonstrate this, in Version 2, I show how you can redefine half and double to use Racket's arithmetic-shift operator. Between this operator and the use of tail recursion, our solution is quite efficient!

an instance of the 'One Does Not Simply...' meme, with Boromir from Lord of the Rings saying 'One does not simply share mutable state'
If you do so lightly, you may end up in Mordor.

Shifting Gears: Data Abstractions Involving 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 it seems to lack 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. So, 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 can 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++.

... revisit these expressions that demonstrate simple I/O in Racket

Racket itself 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. 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.... ™)

Our attention now turns to the idea of mutable data in Racket and in programming languages more generally.

Today, we quickly introduce some of Racket's imperative features for sequencing expressions and mutating data. We consider these in the context implementing data abstractions, which can hide the imperative details of the implementation 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.

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 read a mini-lecture on some of Racket's imperative features for today's session.

Sequencing

Most programming languages provide a way for the programmer to tell a program the order in which to execute a set of statements.

Up to now, the only sequences we have seen in our programs have been sequences of arguments being passed to functions. We have not cared in what order Racket evaluated arguments, because none of the expressions had any side effects; order did not matter. That was a good thing, because it allowed us to focus on the value of the function and not on lower-level machine issues.

(We did learn that and and or do short-circuit evaluation, so we know they work left to right. But they are special forms, with their own evaluation rules.)

What's more, we did not have a good way to tell which order Racket used. If every expression simply returns a value, how can we tell which value is produced first?

Evaluating expressions in a particular order can affect us in other ways, too. One of the greatest barriers to parallel programming on a large scale is the artificial ordering of expressions that we programmers introduce into our solutions. Parallel programmers have to derive solutions in which order matters as little as possible, so that tasks can be assigned to independent processors whenever they become available. Functional programming style "parallelizes" much more naturally than imperative programming.

Digression. For an example of a parallel functional language aimed at high-performance numerical computing, see the programming language Sisal, either at its SourceForge page or in this tutorial.

However, when you need to guarantee that events happen in a certain order, you need a way to indicate that order. Even in Racket.

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 guarantees that Racket will execute the expressions inside it in order.

For more on begin, revisit these expressions from the reading that demonstrate sequencing in Racket.

Quick Exercise: Now that we have seen some of Racket's imperative features, write an expression that tells us whether Racket evaluates function arguments left-to-right or right-to-left.

With begin and display, we now have a way to determine the order in which Racket evaluates arguments to procedures:

> (+ (begin (display "left ") 1)
     (begin (display "right ") 2))
left right 3

So, now we know.

Why haven't we taken advantage of this ability to sequence operations before? Because we have not used any operators with side effects before today!

Sequencing only matters when expressions have side effects.

Unless you are using mutable data or input/output expressions, 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 functional program is usually a sign of an error.

The converse is also true:

Side effects only matters when expressions are in sequence.

Unless you are using a sequences of expressions, what is the point of changing the value of a variable? No one can see the effect!

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.

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))))
define: not allowed in an expression context in:
  (define balance (- balance amount))
Side note: Many 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 the let expression gave us all that we needed.

But Racket screams at us. 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. This requires us to create a local binding using more traditional means, say, a let expression, to accomplish the task.

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:

Defining a name and changing the value of a named object are different activities and so 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 Foos!

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.

a red exclamation point inside a thought bubble
Bang!

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.

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!

Wrap Up