Of Closures and Objects

- ruby closures oop fp

Of closures and objects

A couple of months ago one of my coworkers posted the following little koan (source) in our Slack chat and mentioned that he himself has not yet attained enlightenment:

The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said “Master, I have heard that objects are a very good thing — is this true?” Qc Na looked pityingly at his student and replied, “Foolish pupil — objects are merely a poor man’s closures.”

Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire “Lambda: The Ultimate…” series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.

On his next walk with Qc Na, Anton attempted to impress his master by saying “Master, I have diligently studied the matter, and now understand that objects are truly a poor man’s closures.” Qc Na responded by hitting Anton with his stick, saying “When will you learn? Closures are a poor man’s object.”

At that moment, Anton became enlightened.

Never one to miss a chance to put on my teaching hat, I came up with the following little example and the accompanying explanation and now finally took the time to turn it into a blog post.

Basic premise

A closure “closes” over it’s environment, so it combines state (said environment) and behavior (i.e. what the closure actually does when executed). It’s basically an object with a single method, which in the case of Ruby is call (also aliased to .()). Does that sound familiar? It should, because after all an object also consists of state (instance variables) and behavior (methods).

Example code

# Closure
def make_counter
  i = 0
  -> { i += 1 }
end

counter1 = make_counter
counter1.()
#=> 1
counter1.()
#=> 2
counter1.()
#=> 3
counter2 = make_counter
counter2.()
#=> 1

# Object
class Counter
  def initialize
    @count = 0
  end

  # Let's name this method `call` for symmetry with the closure
  def call
    @count += 1
  end
end

counter3 = Counter.new
counter3.()
#=> 1
counter3.()
#=> 2
counter4 = Counter.new
counter4.()
#=> 1

Looking at make_counter the assignment of a starting value to i sets up the environment that will be “closed over”. It’s not hard to see how this essentially equates to object initialization. We then return a lambda, which has an initial state (i == 0) and a method (.call) to manipulate this state in order to produce the next number. For our code’s users, counter1 and counter2 which were created with *make_counter *behave in exactly the same way as counter3 and counter4 which are instances of the Counter class.

Doubt

At this point my colleague started to understand where this was headed, but he still had his doubts:

oh, i see, but it is a function not an object…

This is obviously true, but apart from lambdas being regular objects in Ruby, our counters are more than “just” functions. Like objects they have internal state (the closed over i variable) and a way of modifying said state (calling the lambda). This is functionally (no pun intended) equivalent to an object that has an instance variable and a method for changing it.

Enlightenment

OK I see 🙂

The term “enlightenment” is of course used jokingly here, but my coworker finally did understand this parable, and I count this as another small victory in my ongoing quest of convincing people that functional programming and object-oriented programming are orthogonal, as opposed to say functional vs imperative programming.