in some ways a closure is just like an object that contains state and
responds to the single method #call
. for example:
# Makes counter objects that increment by 1 every time it is called
class Counter
def initialize
@count = 0
end
def call
@count += 1
end
end
counter = Counter.new
counter.call # => 1
counter.call # => 2
counter.call # => 3
# returns a closure (Proc) we can call to count up
def make_counter
i = 0
-> { i += 1 }
end
closure_counter = make_counter
closure_counter.call # => 1
closure_counter.call # => 2
closure_counter.call # => 3
the key to how this works is the concept of "binding" and an "environment".
Ruby, as expected, has a class Binding
to implement this concept.
Objects of class Binding encapsulate the execution context at some particular place in the code and retain this context for future use. The variables, methods, value of self, and possibly an iterator block that can be accessed in this context are all retained.
These binding objects can be passed as the second argument of the Kernel#eval method, establishing an environment for the evaluation.