• 20 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 10/4/17

Blocks, Procs, and Lambdas

Log in or subscribe for free to enjoy all this course has to offer!

 

So far, we've looked at how methods represent little pieces of code that can be run by different objects when needed. But sometimes you need to be able to reuse a piece of code that isn't a method by itself, or you need to be able to pass a piece of code into another method. To do this, Ruby has something called *blocks.

We learned about how methods take arguments. You can pass arguments in as comma-separated-values in parentheses when you call a method on an object:

my_object.some_method(these, are, arguments)

Or, if it's not too confusing or syntactically ambiguous, you can leave the parentheses off for a cleaner look:

my_object.some_mehtod these, are, arguments

but there's a special type of argument for when you need to pass in code itself as an argument to a function. To do this you need a way to tell Ruby "here's a little bit of code that I want you to run for me later." To do that, you use a block. Blocks go at the end of the method call, and can be written on multiple lines, starting with the worddoand ending with the wordend:

my_object.some_method(these, are, arguments) do
  puts "this is my block"
  puts "I can put any kind of code in here that I want"
end

Instead of the wordsdoandend, you can use curly brackets to delineate your block. Usually people only do this when the block takes up just one line:

my_object.some_method(these, are, arguments) { puts "this is my one-line block" }

Lots of Ruby methods take blocks. A classic example is Ruby'sArray#eachmethod:

[1,2,3].each do |number|
  puts number
end
1
2
3

Since this is just one line, it might be easier on the eyes to write it with the curly braces:

[1,2,3].each { |number| puts number }
1
2
3

The two ways of writing blocks do exactly the same thing.

Try experimenting with different kinds of code inside the block.

How to use blocks in your own Ruby methods

We know how to pass blocks into Ruby methods. So how do you use a block in a Ruby method that you write?

Since the point of blocks is to save pieces of code to be run later, you need a way to run the code in the block. You can do that with the keywordyield:

def my_method(arg1, arg2)
  puts arg1
  puts arg2
  yield
end

my_method("one", "two") do
  puts "three is being printed by the block"
end
=> "one"
=> "two"
=> "three is being printed by the block"

Since blocks are a special type of argument, you can also specify them in the arguments part of your method definition. They always come at the end of the arguments, and have a special "&" character in front of their argument name:

def my_method(arg1, arg2, &my_block)
  # ...
end

This "magically" turns your block into an object that you can pass around. Let's see what that object looks like when we inspect it:

def inspect_my_block(&my_block)
  puts my_block.inspect
end

inspect_my_block do
  puts "foo"
end

=> #<Proc:0x007fc5a4055340@(irb):5>

AProcis just an object that wraps a block. It has useful methods likecallthat let you run the block, so you can do this:

def my_method(arg1, arg2, &my_block)
  puts arg1
  puts arg2
  my_block.call
end

my_method("one", "two") do
  puts "three is being printed by the block"
end
=> "one"
=> "two"
=> "three is being printed by the block"

That's basically the same as callingyield.

Blocks take arguments

Maybe you noticed when we did

[1,2,3].each do |number|
  puts number
end
=> 1
=> 2
=> 3

that the element of the array was captured by the variable name "number" in between those two vertical lines, or *pipe characters. Just like methods, blocks can take arguments. Instead of putting them in between parentheses, it puts them in between pipe characters. Just like arguments, you can have more than one:

my_method("one", "two") do |block_arg1, block_arg2|
  puts block_arg1
  puts block_arg2
}

when calling or yielding your block, you can pass values in for these variables. Using yield, it looks like this:

def yield_foo_and_bar
  yield "foo", "bar"
end

yield_foo_and_bar do |block_arg1, block_arg2|
  puts block_arg1.capitalize
  puts block_arg2.upcase
end
=> "Foo"
=> "BAR"

 

UsingProc#call,it looks like this:

def call_with_foo_and_bar(&my_block)
  my_block.call "foo", "bar"
end

call_with_foo_and_bar do |block_arg1, block_arg2|
  puts block_arg1.capitalize
  puts block_arg2.upcase
end
=> "Foo"
=> "BAR"

In either case, we've created a method that expects a block of code that in its turn expects two arguments. Our method always supplies the argumentsfooandbar. When we call this method, we can pass in any block we want, and it will always be called or yielded, but it will always getfooandbaras arguments.

Let's see what happens if our proc doesn't use all the arguments passed in:

def yield_foo_and_bar
  yield "foo", "bar"
end

yield_foo_and_bar do |block_arg1|
  puts block_arg1.capitalize
end
=> "Foo"

Our methodyield_foo_and_baris yielding our block with two arguments, but our block only uses one, and no one seems to care. Ruby just runs our block as-is, and it only uses the first argument. The same thing happens if we useProc#callinstead ofyield.Procs are more relaxed than methods. When you call them without the full number of arguments, they just assume you meant to do that.

We know now that a block can become aProcobject by naming it with a preceding&in the method arguments. You can also do that on your own by usingProc.new:

my_proc = Proc.new { |block_arg1| puts block_arg1.upcase }
my_proc.call "foo"
=> FOO

In either case, if you have a proc object, you can pass it as a block to a method that expects a block, by putting the & in front of it. Given the method:

def my_method(&my_block)
  my_block.call "foo"
end

the following:

my_proc = Proc.new { |block_arg1| puts block_arg1.upcase }
my_method(&my_proc)

is the same as

my_method{ |block_arg1| puts block_arg1.upcase }

 

Lambdas

Remember when I said Procs are more relaxed than methods, because they can be called (or yielded) with more arguments than they use? You couldn't do that with a method:

def foo_method(one_argument)
  puts one_argument
end
foo_method("two arguments", "please")
=> ArgumentError: wrong number of arguments (given 2, expected 1)

What if you want this behavior with your Procs? Well, you'd be in luck, because it turns there's a special kind of less-relaxedProccalled aLambda, and it does exactly that. You can create them with thelambdakeyword, or its alias->:

my_proc = Proc.new { |one_argument| puts one_argument.capitalize }
my_proc.call("foo", "bar")
=> "Foo"
my_lambda = lambda { |one_argument| puts one_argument.capitalize }
my_lambda.call("foo", "bar")
=> ArgumentError: wrong number of arguments (given 2, expected 1)

Lambdas behave like methods that aren't attached to an object. In other languages, this is called a function.

Callingreturnfrom inside a block

We'll conclude our discussino of blocks by looking at how the keywordreturnbehaves inside them.

Remember that a method has a return value, which is just the last line in the method:

def my_method
  "foo"
end

my_method
=> "foo"

This changes if the wordreturnis encountered. Atreturn, the method stops executing, and provides that line's value as the return value of the method:

def my_method
  return "bar" # the method stops executing after this line
  "foo" # never runs
end

my_method
=> "bar"

 

Blocks also return their last line by default as the value of calling the block. If you want to specify some other return value, don't use the wordreturn, because that will probably cause an error, or another unexpected behavior:

def my_method
  yield
  "foo"
end
my_method do
  return "bar" 
end
LocalJumpError: unexpected return

Instead, use the wordnextinside the block, andreturnin the method, should you need it:

def my_method
  return yield
  "foo"
end
my_proc = Proc.new do
  next "bar" 
end
my_method &my_proc
=> "bar"

In lambdas, on the other hand, you usereturn, just as if it were its own method:

def my_method
  return yield
  "foo"
end
my_lambda = -> do
  return "bar"
end
my_method &my_lambda
=> "bar"

What's the point of all this?

Blocks are a huge part of Ruby. Because Ruby doesn't have native functions, blocks do the heavy lifting of functional operations like iterating over arrays or passing custom code to an object to be run at a later date. In the next chapter we'll see how this works when we look at theEnumerablemodule and its uses.

Example of certificate of achievement
Example of certificate of achievement