Blocks in Ruby

Understanding Blocks and the Difference in Proc and Lambda

4 min read

Update: I've since revised, polished and published a much more in-depth post on this topic, check it out here:

Blocks, Procs, and Lambdas: A Beginner’s Guide to Closures and Anonymous Functions in Ruby
Closures and anonymous functions are very useful concepts, especially in functional programming. Ruby makes working with them a pleasure via its flavors like blocks, procs, and lambdas. This post provides an in-depth tutorial and shows the subtle differences between them.

If you've been programming in Ruby for a while, you've definitely come across blocks or one of their flavors via a Proc or lambda. Though they seem confusing, they're not that difficult to understand. This blog post explains the concept of blocks and the differences between Proc and lambda.

Blocks

Blocks are one of the coolest features of Ruby. Think of them as functions. You can store them in variables, wrap in an object, pass them around to other functions, and call them dynamically. You can use code blocks to implement callbacks, just like in JavaScript.

Here are two ways to define blocks. The best practice is to use the first version with braces for single-line blocks and do..end version for multi-line blocks.

# this is a code block
{ puts "hello" } 

# this is also a code block
do
  puts "hello"
  puts "hello world"
end

You can pass a block of code to another method by putting it after the method call. The method then invokes the block using Ruby's yield keyword.

def greet(name)
  puts "hey, #{name}"
  yield  # call the block
end

greet("Matz") { puts "Thanks for creating Ruby" }

greet("David") do 
  puts "Thanks for creating Rails"
end

### Output

# hey, Matz
# Thanks for creating Ruby

# hey, David
# Thanks for creating Rails

You can also pass arguments to the call to yield, and Ruby will pass them to the block.

def add(a, b)
  sum = a + b
  yield sum
end

add(3, 4) { |sum| puts "result: #{sum}" }

add(4, 5) do |sum|
  puts "result: #{sum}"
end

### Output
# result: 7
# result: 9

Proc

A Proc is an object that wraps a piece of code (block), allowing us to store the block in a local variable, pass it around, and execute it later.

There are four ways to convert a block into a Proc object.

  • Pass a block to a method whose last parameter (typically named block) starts with &. In the following example, the variable block points to an instance of Proc class. If you don't pass a block, it will be nil.
def foo(p1, p2, &block)
  puts block.inspect
end

# #<Proc:0x00007fd9ff119848 main.rb:5>
foo(1, 3) { "a block" }

# nil
foo(3, 4)
  • Call Proc.new or Kernel#proc, associating it with a block.
block = Proc.new { |arg| puts "a block with argument: #{arg}" }
block_new = proc { |arg| puts "shorthand block with argument: #{arg}" }
  • Call the method Kernel#lambda and associate a block with it. Notice the appended (lambda) when you inspect the object.
block = lambda { |arg| puts "a block with argument: #{arg}" }

#<Proc:0x00007fa77f04d8b8 main.rb:1 (lambda)>
puts block.inspect
  • Using the -> syntax. It's a shorthand that also returns a lambda.
block_arrow = -> (arg) { puts arg }

# #<Proc:0x00007fb388830fc0 main.rb:7 (lambda)>
puts block_arrow.inspect

Once you have a Proc object, you can execute its code by invoking its methods call, yield, or [].

block = proc { |arg| puts "a block with argument: #{arg}" }

# a block with argument: 10
block.call 10

# a block with argument: 5
block.yield 5

# a block with argument: 2
block[2]

Lambda

The Proc objects created with the lambda syntax (3rd and 4th option above) are called lambdas. To check if a Proc object is a lambda, call the Proc#lambda? method on it.

block = proc { |arg| puts "a block with argument: #{arg}" }
puts block.lambda?  # false

block_lamb = lambda { |arg| puts "a block with argument: #{arg}" }
puts block_lamb.lambda?  # true

block_arrow = -> (arg) { puts "a block with argument: #{arg}" }
puts block_arrow.lambda?  # true

Proc vs. Lambda

Though they might look the same, Procs and lambdas have a few differences.

a) The return keyword

In a proc, the return keyword returns from the scope where the proc itself was defined.

In a lambda, the return keyword just returns from the lambda.

def run_proc
  p = proc { return 10 }
  p.call # returns from the run_proc method
  20
end

def run_lambda
  p = lambda { return 10 }
  p.call # returns 10 and continues method execution
  20
end

result = run_proc
puts result		# 10

result = run_lambda
puts result		# 20

b) Arguments

The second difference between procs and lambdas concerns the way they check their arguments.

A non-lambda proc doesn't care about its arguments.

If the proc is a lambda, Ruby will check that the number of supplied arguments matches the expected parameters.

# Proc

p = proc { |a, b| puts a, b }

p.call
=> nil

p.call 10
10

p.call(10, 20)
10
20

# lambda

l = ->(a, b) { puts a, b }

l.call
Error: wrong number of arguments (given 0, expected 2) (ArgumentError)

l.call 10
Error: wrong number of arguments (given 1, expected 2) (ArgumentError)

irb(main):078:0> l.call(10, 20)
10
20

Generally speaking, lambdas are more intuitive than procs because they’re more similar to methods. They’re pretty strict about arity, and they simply exit when you call the return keyword.

For this reason, many Rubyists use lambdas as a first choice unless they need the specific features of procs.

That’s it. I hope you liked this article and have a better understanding of Ruby blocks. If you would like to receive future posts directly in the email, please subscribe below. If you have any questions, feedback, or need help with anything, please send me an email. I look forward to hearing from you.

You might also enjoy these articles: