All About Blocks, Procs, and Lambdas in Ruby

So, what are blocks, procs, and lambdas and why should you care about them? To put it simply, they one of the coolest aspects of the Ruby language and they are the sole reason why we have the Enumerable class and it's really nifty methods like map which takes a list and a method and returns a new list where each item has been ran through the supplied method. Very cool. But, were getting ahead of ourselves. Before we can understand how cool methods like map work we fist need to define what a block is.

Blocks

A block in Ruby is any code enclosed by {} or the keywords do end. All blocks in Ruby are actually closures also called lambdas or procedures. A closure is a stored function with it's own internal variable scope and storage. So, they are like little programs within programs that can be passed into functions or returned by functions. So the title of this post is 'All About Blocks, Procs, and Lambdas in Ruby', but it could actually be shortened to just 'All About Blocks in Ruby'. The other keywords were added to the title because these terms are often used interchangeably and because despite there almost identical functionality there are slight differences between these three constructs in Ruby which will be discussed later. Let's look at a simple example.

def mantra yield 5 end mantra { |n| puts 'Take it easy.' * n }

An example of sending a block to a method.

Basically, what we are doing here is sending an anonymous block, the stuff between the curly braces, to the function mantra which then runs that code by using the keyword yield. The anonymous block requires a single parameter which is supplied by the yield method's argument of 5. Now this is a trivial example, but it forms the basis for something like this.

def mapping list new_array = [] for element in list new_array << (yield element) end new_array end array = [1, 2, 3, 4, 5] array_2 = mapping array do |element| element * 2 end puts "#{array} => #{array_2}" # => [1,2,3,4,5] => [2,4,6,8,10]

An example of how a block can be used to create a mapping function.

We just created our own mapping function! Blocks can be really handy in getting rid of a lot of repetitive coding. For example, if you need to do testing where a piece of code is ran and then it's output or errors are formatted and printed you can simply wrap that code into a block and then send it to a testing function. If you want to send a query to a database and do error checking just wrap your query function within a block and send it to a database connecting function. The possibilities are endless. Now that we have covered block functionality lets go over a few notes about block syntax and a few gotchas.

Block Syntax

If you want to send arguments along with an anonymous block you can do this two different ways. If you use bracket notation to define a block the arguments must be put inside parenthesis. If you use do end notation no parenthesis are required.

def mantra (chant_1,chant_2) puts chant_1 puts chant_2 yield 5 end mantra('ohm','ohm') { |n| puts 'take it easy.' * n } # or mantra 'ohm', 'ohm' do |n| puts 'take it easy.' * n end

An example of sending arguments along with an anonymous block to a function.

A block can also be sent to a function and stored in a parameter by using an & before the last parameter name within the function header. Please note that this creates a procedure object which is then stored within that variable name and therefore you will have to use .call instead of yield.

def mantra (chant_1,chant_2,&c3) puts chant_1 puts chant_2 c3.call 5 end mantra('ohm','ohm') { |n| puts 'take it easy.' * n }

An example of sending an anonymous block to a function which is then converted into a procedure and stored in a variable c3.

So far we have only been creating procedures by sending anonymous blocks into functions and then using the & syntax to convert these blocks into procedures. To create a procedure object and store a block of code inside it use the lambda, proc, or Proc.new keywords.

def mantra chant_1, chant_2, c3 puts chant_1 puts chant_2 c3.call 5 end mantra_procedure = proc { |n| puts 'take it easy.' * n } # proc could be replaced by lambda or Proc.new mantra 'ohm', 'ohm', mantra_procedure

An example of creating a procedure object called mantra_procedure and storing a block of code within it.

Finally, bloc_given? can be used within methods or functions to check and see if a block was submitted or not. This can be usefully when you want a method to have a default behavior which can subsequently be overwritten by a submitted block. This behavior is commonly seen with the sort method which will sort an array or object in ascending order, but it's behavior can be changed by submitting an anonymous block.

Block Gotchas

Always remember that variables defined in the main script are actually properties of an object from the Object class which all other classes are subclasses of. This means that every block can access and change any variable defined within the main script! If you use the variables passed into a block you are fine as these are locally defined, however, if you create any variables within a block and they just happen to be the same name as a variable in your main script you could accidentally change their values. If you are going to use variables that are not passed into a block define them within the block header using block local variable syntax which will make these declared variables local.

def mantra yield 5 end mantra { |n; new_n| new_n = n - n % 2 # we only chant in even increments puts 'Take it easy.' * n }

An example of using block local variable syntax to create a locally defined variable within a block.

Basically, any variables listed after the semicolon and between the bars becomes a local variable within the block protecting any variable defined in the main body of the program which may have the same name.

You know how I said blocks, procs, and lambdas are all the same, well, not exactly. Blocks, procs, and lambdas are all closures, however, procs and lambdas are specifically instances of the Proc class and therefore not the same as blocks. Many methods, most notably enumerable methods, only take anonymous blocks. If you have a stored procedure that you would like to send to one of these methods use & before the argument's name when sending it to the method. This basically does the opposite of an & within a method header, it converts a procedure to a block.

# as an anonymous block puts [1,2,3,4,5].map { |n| n * 2 }.to_s # as a procedure p = proc { |n| n * 2 } puts ([1,2,3,4,5].map &p).to_s

An example of converting a stored procedure into a block before sending it to an enumerable method.

Conclusion

So, to sum up blocks, procs, and lambdas in Ruby just remember that they are basically methods with their own variable scope and variable storage that can be passed into functions or be created by functions. This capability is what makes the Enumerable class possible and so much more. It is powerful stuff worth learning so play around with them in irb be creative and have fun!