DEV Community

Victor Hazbun
Victor Hazbun

Posted on • Updated on

Simulating Classes with Closures in Ruby

Let's say you want to build a very simple counter program. The counter program can 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
Enter fullscreen mode Exit fullscreen mode

There should not be anything suprising with this example. So 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 definetly can. Create a new file called lambda_counter.rb and fill it 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
Enter fullscreen mode Exit fullscreen mode

Here, Counter is a lambda. Line 2 declares x, the state of the counter, and initializes it to zero. Line 3 creates a lambda that returns the current state of the counter. Line 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)>,
:increment=>#<Proc:0x007fa92904e910@/counter.rb:6 (lambda)>,
:increment=>#<Proc:0x007fa92904e898@/counter.rb:8 (lambda)>}`
Enter fullscreen mode Exit fullscreen mode

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

>> c1[:increment].call
=> 1
>> c1[:increment].call
=> 2
>> c1[:increment].call
=> 3
>> c1[:increment].call
=> 2
>> c1[:get_x].call
=> 2
Enter fullscreen mode Exit fullscreen mode

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)>,
:increment=>#<Proc:0x007fa92a1fcc70@/counter.rb:6 (lambda)>,
:decrement=>#<Proc:0x007fa92a1fcc48@/counter.rb:8 (lambda)>}
>> c2[:get_x].call
=> 0
>> c1[:get_x].call
=> 2
Enter fullscreen mode Exit fullscreen mode

Both c1 and c2 get their own x. So there you have it: it is entirely possible to
have objects without classes. In fact, this technique is often used in JavaScript
to make sure that variables do not leak out and inadvertently become overridden
by some other function or operation.

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 from here. 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. As the counter example
has shown, closures restrict access to the variables they wrap. This technique
will come in handy in the later chapters.

If you have done any amount of JavaScript programming, you would most
definitely have encountered the use of callbacks. When used judiciously,
callbacks are a very powerful technique.

Top comments (0)