block, proc, and lambda in Ruby

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.

10 min read

If you've been programming in Ruby for a while, you've definitely come across blocks, or one of their flavors via Procs and lambdas. If you're coming from another programming language, you might recognize them as closures, anonymous functions, or even arrow functions.

Though all these concepts and fancy words may sound overwhelming and confusing, especially if you're a beginner, the basic concepts are not that complicated. What's more, Ruby makes it a pleasure to work with them, with its elegant, beautiful, and expressive syntax.

In this article, we'll learn just how much fun it is to work with closures and anonymous functions in Ruby. We'll also explore why and when you should use them in your code.

This is a huge article. Here's the list of topics we'll explore:

Let's begin. We've got a lot of ground to cover.

What are Closures and Anonymous Functions?

To quote Jon Skeet, one of my programming heroes from my .NET days,

To put it very simply, closures allow you to encapsulate some behaviour, pass it around like any other object, and still have access to the context in which they were first declared.

This allows you to separate out control structures, logical operators, etc. from the details of how they're going to be used. The ability to access the original context is what separates closures from normal objects.

The Beauty of Closures

If you're an absolute beginner and the above paragraphs went over your head, don't worry. Here's an example that might help you understand better.

Consider a simple function in Ruby that adds two numbers.

def add(a, b)
  a + b
end

You can run this as follows:

add(3, 4) # returns 7

What if instead of executing this function immediately, you want to store it in a variable, or pass it to another function, and execute it later? This would let you do so many cool things, like deciding at runtime whether to call it or not based on certain conditions, such as user input or application state.

Closures (also called anonymous/arrow functions) let you accomplish this. In Ruby, they're known as blocks, procs, or lambdas.

There are a few big benefits of using blocks, procs, and lambdas in Ruby. You can:

  1. Assign them to variables and pass them around.  
  2. Capture variables from outside their scope (hence called closures).
  3. Implement callbacks (just like JavaScript).

Let's inspect the blocks first.

A Block of Code

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.

There are two ways to create blocks in Ruby.

# this is a code block (used for single-line code)
{ |name| puts "hello #{name}" } 

# this is also a code block (used for multi-line code),
do |name| 
  puts "hello #{name}"
  puts "how are you?"
end

Although you can use them interchangeably, the best practice is to use the first version with braces { } for single-line blocks and do..end  version for multi-line blocks.

But you don't use blocks in isolation, as shown above. You have to pass the block to another method by putting it after the method call. In the example below, we're calling the perform method and passing a block to it.

perform { |a, b| a + b }

# OR

perform do |a, b| 
  a + b
end

The block won't be executed immediately. The perform method has to invoke the block using Ruby's yield keyword.

def perform
  puts 'performing the operation you provided via block'
  yield  # executes the block
  puts 'operation completed'
end
💡
Does that make sense? We're literally passing an operation (a block of code) to a method and calling it at a later time when that method actually runs.

Here's another example that greets a person given their name, and also prints a custom greeting message that the caller can pass via a block.

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

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

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

# Output
#
# hey, Matz
# Thanks for creating Ruby
# bye!
#
# hey, David
# Thanks for creating Rails
# bye!

What happens if a method calls yield when the caller has not provided a block?

def perform
  puts 'performing'
  yield
  puts 'performed'
end

# oops! forgot to provide the block...
perform

# Output
# no block given (yield) (LocalJumpError)

As you can see, Ruby throws an error: "no block given".

To prevent this, you should always check if a block was passed before using the yield keyword. For this, use the block_given? method defined in the Kernel module (so you can call it as it is, without any object).

def build_response
  response = create_response_somehow
  yield response if block_given?
  do_cleanup
end

build_response do |response|
  # handle response
end

Keep in mind: Ruby won't execute the block unless and until you execute it with the yield keyword.

The above example also demonstrated that you can pass one or more arguments to the call to yield, and Ruby will pass them to the block. Let's look at another example:

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

I think that's enough information about blocks for now. Let's explore their cousins, procs and lambdas.

Procs and Lambdas

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. A lambda is very similar to a proc (in fact, it's an instance of Proc), with a few differences that we'll see later.

You can create a Proc instance using the following syntax. The Proc objects created with the lambda syntax (3rd and 4th options below) are called lambdas.

# This is a Proc object
Proc.new { |a, b| a + b }

# or a Proc shorthand
proc { |a, b| a + b }

# A Proc known as a lambda
lambda { |a, b| a + b }

# also a lambda (arrow version)
->(a, b) { a + b }
Interestingly, lambdas are instances of Procs. So behind the scenes, they're all Procs, with a few differences between them.

Procs and lambdas differ from the blocks in the sense that you can directly assign them to a variable that can be passed around. For example,

adder = ->(a, b) { a + b }

# pass to another function
perform(adder)

# call it
result = adder.call(3, 4) # OR adder[3, 4]

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

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

operation.call 10  # a block with argument: 10

operation.yield 5  # a block with argument: 5

operation[2]       # a block with argument: 2

If you wanted to write the previous greeting example using a lambda or a Proc, you have to treat it as a separate argument in the function definition.

def greet(name, handler)
  puts "hey, #{name}"
  
  handler.call
  
  puts "bye!"
end

greet "David", lambda { puts "Thanks for creating Rails" }
greet "Taylor", -> { puts "Thanks for creating Laravel" }
greet "Adam", proc { puts "Thanks for creating Tailwind CSS" }
greet "Anders", Proc.new { puts "Thanks for creating C#" }

In fact, when you pass a block to a method with a parameter with the & character as a prefix, the block becomes a Proc. This lets you make the block explicit and treat it as a variable (which you can also pass around), as follows:

# blk is an instance of Proc
def build_response(data, &blk)
  response = create_response_somehow(data)
  blk.call(response)
  do_cleanup
end

build_response(data) do |response|
  # handle response
end

Keep in mind: The block should always be passed as a last argument in the method. Otherwise, you will receive an error.

To check if a Proc object is a lambda, call the Proc#lambda? method on it.

proc_one = proc { puts 'a proc' }
puts proc_one.lambda?  # false

proc_two = lambda { puts 'a lambda' }
puts proc_two.lambda?  # true

proc_three = -> { puts 'a lambda literal' }
puts proc_three.lambda?  # true

What's The Difference Between Procs and Lambdas in Ruby?

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

Difference 1: The return keyword

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

I know that didn't make any sense. Let's look an example:

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

Difference 2: Checking the 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.

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

p.call
# nil

p.call 10
# 10

p.call(10, 20)
# 10
#  20

In contrast, for a lambda, Ruby will check that the number of supplied arguments matches the expected parameters.

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)

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.

Next, let's inspect why and when you might want to use blocks in Ruby.

Capture Variables Outside Scope

This example shows how a lambda can capture variables outside its scope.

name = 'ruby'

printer = -> { puts name }

printer.call  # ruby

This might seem trivial. However, if you look closely, the variable name exists outside the scope of the lambda, and yet the lambda can access it.

A regular Ruby function can not access variables outside its scope.

name = 'ruby'

def print
  puts name
end

# undefined local variable or method 'name' for main:Object (NameError)
print
💡
This ability to access variables from a scope outside of the closure's definition is called capturing from the environment.

This is a pretty powerful feature, and Ruby's metaprogramming takes it to the next level by allowing you to create classes and modules on the fly, using variables defined outside the scope of regular classes and modules.

num = 1

Runner = Class.new do
  puts "#{num} from class"

  define_method :run do
    puts "#{num} from method"
  end
end

Runner.new.run

# Output
# ==============
# 1 from class
# 1 from method

This is also known as "flattening the scope". For more details, check out the following article:

How to Access Variables Outside Scopes in Ruby
In C# and Java, variables from the outer scope are visible to the inner scope. In Ruby, scopes are sharply separated, and nested visibility is not allowed. As soon as the control enters a new scope, i.e. a class, module, or a method, the previous bindings are replaced by

Finally, you can pass these blocks, procs, and lambdas as callbacks, just like in JavaScript. Rails abundantly uses this feature. Let's see how.

Pass Them Around as Callbacks

At this point, you might be wondering when you may want to use a block or a lambda. After all, if all you're trying to do is this:

name = 'ruby'

printer = -> { puts name }

printer.call

You could just do this, right?

name = 'ruby'

puts name

Well, this is a trivial example just to make a point. However, in real-world programming, you'd do something like this:

def scope(name, condition)
  puts 'condition: ' + name.to_s
  
  if condition.call
    puts 'creating scope'
  end
  
  puts 'scope was added'
end

# somewhere else in your code
scope :custom, -> { user.admin? }

It is the power of passing around custom logic (check if user is an admin) as callbacks that make blocks so important. They allow a caller to decide what logic executes inside the body of a function, and when.

Rails makes abundant use of blocks, procs, and lambdas. As you can see below, we're directly passing a lambda to the scope and validates methods.

class User < ApplicationRecord
  scope :confirmed, -> { where(confirmed: true) }
end

class User < ApplicationRecord
  validates :email, if: -> { phone_number.blank? }
end

Blocks and lambdas make your Ruby code very expressive and concise, also enabling you to create domain-specific languages (DSL) like Rails.

If you're a JavaScript developer, think of the above code as similar to this code:

scope(CONFIRMED, () => where({ confirmed: true }))

validates(EMAIL, { if: () => phone_number.blank? })

You can notice how pretty it looks in Ruby.

Practical Use Cases

To solidify our understanding, let's look at a few common use cases for blocks.

Run Common Code Before and After

Let's say you always want to execute some code before and after a function runs. For example, creating a lock on a file, performing some operation that could vary, and then releasing a lock.

def handle
  before_logic
  
  # varying logic
  
  after_logic
end

You could parameterize the varying operation via a block, and then call the before and after code around the block execution, as follows:

def handle
  before_logic
  
  yield
  
  after_logic
end

Now you can call the handle method as follows:

handle do
  # work with a resource
end

You'll have the guarantee that some logic was executed before and after the body of the block.

Benchmark a Piece of Code

Let's say you want to calculate how long it took to perform a few pieces of code blocks. Now you could gather the start and end times for each of those blocks to measure speed, but a better solution is to create a function that accepts a block and handles the measurements.

def benchmark(name, operation)
  start_time = Time.now.to_i
  operation.call
  duration = Time.now.to_i - start_time
  
  puts "#{name} took #{duration} seconds"
end

Now you can use this function as follows:

benchmark :nap, -> { sleep(2) }

# nap took 2 seconds

As you can see, blocks are a very useful tool to make your APIs more fluent and the code more expressive and concise.

💡
Once you understand that blocks and lambdas in Ruby are just closures and anonymous functions with much less ceremony, you'll find it much easier to read Rails and in general, Ruby code. 

I hope you liked this article and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.