Clicky

Harry R. Schwartz

Software engineer, nominal scientist, gentleman of the internet. Member, ←Hotline Webring→.

· 1B41 8F2C 23DE DD9C 807E A74F 841B 3DAE 25AE 721B

Building an Object System from Closures

Published 18 Aug 2015. 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: