Harry R. Schwartz

Code writer, sometime Internet enthusiast, attractive nuisance.

Vancouver

British Columbia

Canada

vegan


Building an Object System from Closures

hrs

Published .
Tags: computer-science, lisp.

When you’re first learning about functional programming, you sometimes hear that objects are equivalent to closures. What the hell does that mean?

Let’s start with definitions: an object is a thing with behavior and state. An object’s behavior is just its public interface; it’s the set of messages that the object can receive. An object’s state consists of zero or more internal variables that methods on the object can manipulate. In other words, an object’s behavior is defined by its methods and its state is defined by its instance variables. So far, so good.

If you’ve got a simple functional programming language, you can model these concepts really neatly! An object’s behavior can be defined by a function that accepts messages and dispatches them, and its state is a closure over that function. Using this model, a constructor is just a function that returns such a closure.

That means that if you’ve got a language with closures and first-class functions (which is darn near all of them these days) then you can build your own object system.

Let’s get a little more concrete and take a look at an example (using Scheme as our implementation language):

(define (make-counter)
  (let ((count 0))
    (lambda (message)
      (case message
       ('get count)
       ('increment (set! count (+ 1 count)))
       ('reset (set! count 0))
       (else (error "Unknown method:" message))))))

This is a “constructor” called make-counter that returns a function. We can call that function and pass it a symbol (that is, message) which determines which “method” should be called.

The case expression dispatches on the message to determine the function’s behavior (we can call this kind of expression a dispatch table). That behavior might include manipulating count.

We can use it like so:

(define counter (make-counter))

(counter 'get) ; => 0

(counter 'increment)
(counter 'increment)
(counter 'increment)
(counter 'get) ; => 3

(counter 'reset)
(counter 'get) ; => 0

(counter 'undefined-behavior) ; ERROR! "Unknown method: undefined-behavior"

Cool! It looks like counter is responding to messages and managing internal state, which sounds an awful lot like an object to me. Mission accomplished!

This example was intentionally simple, but there’s plenty that we could do to extend it:

  • We can add more state and more behavior by adding items to the let and case statements, respectively.
  • None of our messages currently accept arguments. We could extend the anonymous function to take an optional argument list (with syntax like (lambda (message . args) ... )) and modify the appropriate elements in the dispatch table to parse args.
  • Inheritance? Not too difficult! We’ve got a few options here, but they all revolve around modifying the else case in the dispatch table to delegate messages to a “parent” object. We could create this parent object in the let statement and treat it like any other piece of internal state.
  • To extend the previous point, we could also go crazy and pass the parent object in as an argument to the constructor, which lets us assign an object’s parent on a case-by-case basis: need a 'car that inherits from 'elephant? Easy: (make-car (make-elephant)). Boom: we just built an elephant car.
  • Our objects don’t currently provide any introspection, so we can’t tell what messages they respond to. We could provide that by making the dispatch table into its own object, which could 'register new behaviors and test whether it 'responds-to? a given message. Our objects could delegate their own 'responds-to? calls to their respective dispatch table.

You might like these textually similar articles: