Blog icon

Using Ruby Closures to simulate classes

By Victor Hazbun,
Victor Hazbun
Scroll down to read

A technique commonly used in JavaScript is to use closures in your objects instead of classes. This is done to ensure that variables do not leak out and inadvertently become overridden by some other function or operation.

This technique can also be used in Ruby. The scoping rules of Ruby are such that when a lambda is defined, that lambda also has access to all the variables that are in scope.

Let's say you want to build a simple counter program. This counter program will do the following:

  • Get the current value of the counter.
  • Increment the counter.
  • Decrement the counter.

This is the essence of what most classes do: retrieve and modify data. Here is one possible implementation of a Counter class:

Class Counter

    def initialize
        @x = 0
    end

    def get_x
        @x  
    end

    def increment
        @x += 1
    end

    def decrement
        @x -= 1 
    end
end

# Here is a sample run in irb:

    >> c = Counter.new
    => #<Counter:0x007f9335939848 @x=0>
    >> c.increment
    => 1
    >> c.increment
    => 2
    >> c.get_x
    => 2
    >> c.decrement
    => 1
    >> c.decrement
    => 0
    >> c.decrement
    => -1

There should not be anything surprising with this example. Let's add some constraints.

Imagine if you didn't have the ability to create classes. Could you still write a counter program? With lambdas, you most definitely can.

Create a new file called lambda_counter.rb with the following code:

Counter = lambda do # 1 

    # x = 0 # 2

    get_x = lambda { x } # 3

    increment = lambda { x += 1 } # 4

    decrement = lambda { x -= 1} # 5

    {get_x: get_x, increment: increment, decrement: decrement} # 6
end # 8

Here, Counter is a lambda. Line 2 declares x, the state of the counter, and initialises it to zero. Line 3 creates a lambda that returns the current state of the counter. Lines 4 and 5 both modify the state of the counter, by increasing or decreasing the value of x respectively.

It should be apparent to you now that x is the free variable. Finally, on line 6, the return result of the most outermost lambda is a hash whose keys are the names of the respective lambdas.

By saving the return values, you can get a reference to the respective lambdas and manipulate the counter. And manipulate the counter you will!

Load the lambda_counter.rb file in irb:

% irb -r ./lambda_counter.rb

>> c1 = Counter.call
=> {:get_x=>#<Proc:0x007fa92904ea28@/counter.rb:4 (lambda)>,
:incr=>#<Proc:0x007fa92904e910@/counter.rb:6 (lambda)>,
:decr=>#<Proc:0x007fa92904e898@/counter.rb:8 (lambda)>}`

Counter c1 is a hash where each key points to a Proc. Let’s perform some operations on the counter:

>> c1[:incr].call
=> 1
>> c1[:incr].call
=> 2
>> c1[:incr].call
=> 3
>> c1[:decr].call
=> 2
>> c1[:get_x].call
=> 2

Let’s create another counter, c2. Is c2 distinct from c1? In other words, do they behave like distinct objects?

>> c2 = Counter.call
=> {:get_x=>#<Proc:0x007fa92a1fcc98@/counter.rb:4 (lambda)>,
:incr=>#<Proc:0x007fa92a1fcc70@/counter.rb:6 (lambda)>,
:decr=>#<Proc:0x007fa92a1fcc48@/counter.rb:8 (lambda)>}
>> c2[:get_x].call
=> 0
>> c1[:get_x].call
=> 2

Both c1 and c2 get their own x.

Conclusion

So there you have it: it is entirely possible to have objects without classes. While you probably wouldn’t want to use this technique in your day-to-day Ruby programming, there’s an important lesson to be drawn here.

As the counter example has shown, closures restrict access to the variables they wrap.

To find out how reinteractive can turn your web application vision into reality, get in touch with us through our contact form or call us on +61 2 8019 7252.