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

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:

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.

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

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.

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, do so lightly, lest you end up in Mordor.

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

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

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 defines? 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 defines 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 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.

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