Blocks as Closures: Using Outside Variables Within a Code Block

August 13, 2007 · Filed Under Code Blocks and Iteration, Ruby on Rails 

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.