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
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
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)>}`
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
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
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)