The Ultimate Guide to Blocks, Procs & Lambdas

chebyte
Hashdog Blog
Published in
5 min readFeb 2, 2016

Understanding Blocks

Blocks are very prevalent in Ruby, you can think of them as little anonymous functions that can be passed into methods.

Blocks are enclosed in a do / end statement or between brackets, and they can have multiple arguments. The argument names are defined between two pipe | characters.

If you have used each before, then you have used blocks!

Here is an example:

# Form 1: recommended for single line blocks

[1,2,3].each{|num|putsnum}

^^^^^^^^^^^^^

blockblock

argumentsbody

# Form 2: recommended for multi-line blocks

[1,2,3].eachdo|num|

putsnum

end

So how does a method work with a block, and how can it know if a block is available? Well, to answer the first question, you need to use the yield keyword. When you use yield, the code inside the block will be executed.

defprint_once

yield

end

print_once{puts”Block is being run”}

Yield can be used multiple times, which will result in the block being executed as many times as you call yield.

defprint_twice

yield

yield

end

print_twice{puts”Hello”}

It is also possible to use yield with any number of arguments. These arguments can then be used by the block.

defone_two_three

yield1

yield2

yield3

end

one_two_three{|number|putsnumber*10}

# 10, 20, 30

Blocks can also be explicit instead of implicit. What this means is that you can name the block and pass it around if you need to.

Here is an example:

defexplicit_block(&block)

block.call# Same as yield

end

explicit_block{puts”Explicit block called”}

If you try to yield without a block you will get a no block given (yield) error. You can check if a block has been passed in with the block_given? method.

defdo_something_with_block

return”No block given”unlessblock_given?

yield

end

Lambdas vs Procs

Procs & lambdas are a very similar concept, one of the differences is how you create them.

Note: In fact, there is no dedicated Lambda class. A lambda is just a special Proc object. If you take a look at the instance methods from Proc, you will notice there is a lambda? method.

The syntax for defining a lambda looks like this:

say_something=->{puts”This is a lambda”}

Defining a lambda won’t run the code inside it, you need to use the call method for that.

Example:

say_something=->{puts”This is a lambda”}

say_something.call

# “This is a lambda”

There are other ways to call a lambda, it’s good to know they exist, however, I recommend sticking with call for clarity.

my_lambda=->{puts”Lambda called”}

my_lambda.call

my_lambda.()

my_lambda[]

my_lambda.===

Lambdas can also take arguments, here is an example:

times_two=->(x){x*2}

times_two.call(10)

# 20

If you pass the wrong number of arguments to a lambda, it will raise an exception, just like a regular method. But that’s not the case with procs, as demonstrated in the following example.

t=Proc.new{|x,y|puts”I don’t care about arguments!”}

t.call

# “I don’t care about arguments!”

Another difference between procs and lambdas is how they react to a return statement. A lambda will return normally, like a regular method. But a proc will try to return from the current context.

If you run the following code, you will notice how the proc raises a LocalJumpError exception. The reason is that you can’t return from the top-level context.

Try this:

# Should work

my_lambda=->{return1}

puts”Lambda result: #{my_lambda.call}”

# Should raise exception

my_proc=Proc.new{return1}

puts”Proc result: #{my_proc.call}”

If the proc was inside a method, then calling return would be equivalent to returning from that method. This is demonstrated in the following example.

defcall_proc

puts”Before proc”

my_proc=Proc.new{return2}

my_proc.call

puts”After proc”

end

pcall_proc

# Prints “Before proc” but not “After proc”

Here is a summary of how procs and lambdas are different:

  • Lambdas are defined with -> {} and procs with Proc.new {}.
  • Procs return from the current method, while lambdas return from the lambda itself.
  • Procs don’t care about the correct number of arguments, while lambdas will raise an exception.

Taking a look at this list, we can see that lambdas are a lot closer to a regular method than procs are.

Closures

Procs & lambdas also have another special attribute. When you create a proc, it captures the current execution scope with it. This concept, which is sometimes called closure, means that a proc will carry with it values like local variables and methods from the context where it was defined.

They don’t carry the actual values, but a reference to them, so if the variables change after the proc is created, the proc will always have the latest version.

Let’s see an example:

defcall_proc(my_proc)

count=500

my_proc.call

end

count=1

my_proc=Proc.new{putscount}

pcall_proc(my_proc)# What does this print?

In this example we have a local count variable, which is set to 1. We also have a proc named my_proc, and a call_proc method which runs (via the call method) any proc or lambda that is passed in as an argument.

What do you think this program will print? It would seem like 500 is the most logical conclusion, but because of the ‘closure’ effect this will print 1.

This happens because the proc is using the value of count from the place where the proc was defined, and that’s outside of the method definition.

The Binding Class

Where do procs & lambdas store this scope information? Let me tell you about the Binding class.

When you create a Binding object via the binding method, you are creating an ‘anchor’ to this point in the code. Every variable, method and class defined at this point will be available later via this object, even if you are in a completely different scope.

Example:

defreturn_binding

foo=100

binding

end

# Foo is available thanks to the binding,

# even though we are outside of the method

# where it was defined.

putsreturn_binding.class

putsreturn_binding.eval(‘foo’)

# If you try to print foo directly you will get an error.

# The reason is that foo was never defined outside of the method.

putsfoo

In other words, executing something under the context of a binding object, is the same as if that code was in the same place where that binding was defined (remember the ‘anchor’ methaphor).

You don’t need to use binding objects directly, but it’s still good to know this is a thing

Wrapping Up

One thing I didn’t cover is the curry method, this method allows you to pass in some or all of the arguments, if you only pass in a partial number of arguments you will get a new proc with these arguments already ‘pre-loaded’, when all the arguments are supplied then the proc will be executed.

I hope you enjoyed this post! Don’t forget to subscribe in the form below and share this with your friends

Originally published at www.blackbytes.info on February 2, 2016.

--

--