Summary

Closures as (basic) objects

In the last two classes, we established two key concepts:

  1. Conceptual: A closure is a “bundle” of a function and its captured referencing environment. We compared this to a Java object: the captured variables (like tax-rate) are like private fields, and the function code is like a single method.
  2. Implementation: We saw how this works using the Environment Model. Calling a function creates a new frame. If that function returns a lambda, the resulting closure gets a pointer to that new frame, keeping its local variables (e.g., x = 5 in make-adder) “alive” even after the outer function returns.

Our make-adder function was a “factory” that created objects, but these objects could only do one thing:

(define add-5 (make-adder 5))
(add-5 10) ; The *only* thing we can do is call it.

A Java object obj can do many things: obj.method1(), obj.method2(arg), etc. How can we replicate this?

Preliminaries: set! and cond

First we need to introduce two new Scheme commands.

Message passing

Instead of returning a closure that is the method, we will return a closure that is the object itself — like a central dispatcher. To “call a method,” we will pass this dispatcher function a message (like a symbol) specifying the operation we want.

The object’s “instance variables” will be local bindings created by internal defines. The “methods” will be the logic inside the dispatcher’s cond that share and (if needed) mutate this local state.

Example 1: A Simple Counter

Let’s build a counter object that understands three “messages”: 'increment, 'get, and 'reset.

;; `make-counter` is our "class" or "factory"
(define make-counter
  (lambda () ;; the "constructor" takes no arguments

    ;; `count` is the "private instance variable"
    (define count 0)

    ;; This lambda is the "object" we return
    ;; It's a dispatcher that takes a message.
    (lambda (message)
      (cond
        ((eq? message 'increment)
         (set! count (+ count 1))) ; Mutates the captured state

        ((eq? message 'get)
         count) ; Returns the captured state

        ((eq? message 'reset)
         (set! count 0))

        (else
         (error "Unknown message:" message))))))

Using the Counter Object

This is how we interact with our new “object.” Notice the syntactic similarity to OOP:

;; 1. Create two "instances"
(define c1 (make-counter))
(define c2 (make-counter))

;; 2. "Call methods" on c1
(c1 'increment)
(c1 'increment)
(display (c1 'get)) ; Prints 2

;; 3. "Call methods" on c2 (shows encapsulation)
(c2 'increment)
(display (c2 'get)) ; Prints 1

;; 4. c1's state is independent of c2's
(c1 'reset)
(display (c1 'get)) ; Prints 0
(display (c2 'get)) ; Prints 1 (unchanged)

How this works (Environment Model):

Example 2: A Bank Account (with arguments)

That’s great, but methods often take arguments (e.g., deposit(100)). How do we handle that?

We can make our dispatcher lambda accept variable arguments using the (lambda (message . args) ...) syntax. The args will be a list of all other arguments.

;; `make-account` is our "class" factory
(define make-account
  (lambda (initial-balance)

    ;; `balance` is the private "instance variable"
    (define balance initial-balance)

    ;; The returned lambda is the "object"
    (lambda (message . args) ;; args is a list of all remaining arguments
      (cond
        ((eq? message 'deposit)
         (set! balance (+ balance (car args)))
         balance)

        ((eq? message 'withdraw)
         (cond ((>= balance (car args))
                (set! balance (- balance (car args)))
                balance)
               (else
                (error "Insufficient funds!"))))

        ((eq? message 'get-balance)
         balance)

        (else
         (error "Unknown message:" message))))))

Using the Bank Account Object

(define acct1 (make-account 100))

(acct1 'deposit 50)  ; Returns 150
(acct1 'withdraw 20) ; Returns 130
(acct1 'get-balance) ; Returns 130

(acct1 'withdraw 200) ; Raises Error: "Insufficient funds"
(acct1 'get-balance)  ; Returns 130 (state was unchanged)