Blocks as Closures: Using Outside Variables Within a Code Block
Problem
You want to share variables between a method, and a code block defined within it.
Solution
Just reference the variables, and Ruby will do the right thing. Here’s a method that adds a certain number to every element of an array:
def add_to_all(array, number)
array.collect { |x| x + number }
end
add_to_all([1, 2, 3], 10) # => [11, 12, 13]
Enumerable#collect
can’t access number directly, but it’s passed a block that can access it, since number was in scope when the block was defined.
Discussion
A Ruby block is a closure: it carries around the context in which it was defined. This is useful because it lets you define a block as though it were part of your normal code, then tear it off and send it to a predefined piece of code for processing.
A Ruby block contains references to the variable bindings, not copies of the values. If the variable changes later, the block will have access to the new value:
tax_percent = 6
position = lambda do
"I have always supported a #{tax_percent}% tax on imported limes."
end
position.call
# => "I have always supported a 6% tax on imported limes."
tax_percent = 7.25
position.call
# => "I have always supported a 7.25% tax on imported limes."
This works both ways: you can rebind or modify a variable from within a block.
counter = 0
4.times { counter += 1; puts "Counter now #{counter}"}
# Counter now 1
# Counter now 2
# Counter now 3
# Counter now 4
counter # => 4
This is especially useful when you want to simulate inject or collect in conjunction with a strange iterator. You can create a storage object outside the block, and add things to it from within the block. This code simulates Enumerable#collect, but it collects the elements of an array in reverse order:
accumulator = []
[1, 2, 3].reverse_each { |x| accumulator << x + 1 }
accumulator # => [4, 3, 2]
The accumulator variable is not within the scope of Array#reverse_each, but it is within the scope of the block.
Comments
Leave a Reply
You must be logged in to post a comment.
