Ruby blocks, procs & lambdas. What are they?

byGinkSun, 14 Mar 2021

1. Blocks

A block in Ruby is an anonymous, inline piece of code that can be passed as an argument to a method. Blocks are defined using the do and end keywords or curly braces {}. They can be invoked by the method they are passed to using the yield keyword.

Here's an example that demonstrates the use of a block in Ruby:

3.times do
    puts "Hello inside the block"
end
# Hello inside the block
# Hello inside the block
# Hello inside the block

In this example, times method takes a block and execute it with specified number of times. When this code runs, it outputs "Hello inside block" three times.

1.1. Ruby yield keyword

But what really happened inside that times method in the example above? It turns out that was done by a yield keyword inside. Let's implement our own method to understand how it work.

def do_something
    puts "Before yield"
    yield
    puts "After yield"
end

do_something do
    puts "This part is inside block"
end
# Before yield
# This part is inside block
# After yield

Every time we call yield, the block will run, so this is like calling the same method but via block. And we can even pass arguments to yield

def do_another_thing
    yield 1
    yield 2
    yield "halo"
end
do_another_thing do |i|
    puts "Something to print out:", i
end
# Something to print out:
# 1
# Something to print out:
# 2
# Something to print out:
# halo

1.2. Implicit vs Explicit Blocks

  • In Ruby, a block can be passed to a method explicitly by using the & operator followed by the block. For example:
def repeat(times, &block)
  times.times { block.call }
end

repeat(3) { puts "Hello inside the block" }
  • A block can also be passed to a method implicitly, simply by including it in the method call after the arguments.
def repeat(times)
  times.times { yield }
end

repeat(3) { puts "Hello inside the block" }

Any method will take a block as the last argument if we don't declare it explicitly. And when declaring it explicitly, we use block.call instead of yield to execute block code. That's it.

2. Procs

A proc in Ruby is an object that encapsulates a block of code. Unlike blocks, procs can be stored in variables, passed as arguments to methods, and even returned as the value of other methods.

def repeat(times, proc)
  times.times { proc.call }
end

my_proc = Proc.new { puts "Hello inside the proc" }
repeat(3, my_proc)
# Hello inside the proc
# Hello inside the proc
# Hello inside the proc

The proc argument is an instance of the Proc class that encapsulates the block of code { puts "Hello inside the proc" }.

One of the main differences between Procs and blocks is that Procs are objects, and blocks are not. This means that Procs can be assigned to variables, passed as arguments to methods, and stored in data structures, while blocks cannot.

2.1. The proc keyword

We can also use proc keyword to declare a proc. But keep in mind about the difference between surrounding scope. The proc keyword automatically sets the self value of the Proc to the value of self in the surrounding scope, while Proc.new does not.

class MyClass
  def create_proc
    proc { puts self }
  end

  def create_proc_with_new
    Proc.new { puts self }
  end
end

my_obj  = MyClass.new
my_proc = my_obj.create_proc
my_proc.call # => #<MyClass:0x00007f8c8e1a7b98>

my_proc_with_new = my_obj.create_proc_with_new
my_proc_with_new.call # => main

3. Lambdas

A lambda in Ruby is a type of closure that is similar to a proc, but with some important differences. Like procs, lambdas are objects that encapsulate a block of code and can be stored in variables, passed as arguments to methods, and returned as the value of other methods.

We can use lambda keyword or the right arrow -> to declare a lambda. They're the same, just the difference in syntax. ->(x) { ... } is equal to lambda { |x| ... }

Here's an example that demonstrates the use of a lambda in Ruby:

def repeat(times, ld)
  times.times { ld.call }
end

my_lambda = lambda { puts "Hello inside lambda" }
repeat(3, my_lambda)
# Hello inside lambda
# Hello inside lambda
# Hello inside lambda
  • One of the main differences between lambdas and procs is the way they handle arguments. While procs are relatively flexible with respect to the number of arguments they accept, lambdas are more strict and will raise an error if they receive the wrong number of arguments. For example:
my_lambda = lambda { |a, b| a + b }
my_lambda.call(1, 2) # => 3
my_lambda.call(1) # => raises an ArgumentError
  • Another difference is the way they handle the return keyword. While a return statement inside a proc will cause the containing method to return, a return statement inside a lambda will cause the lambda itself to return.
def returns_lambda
  lambda { return "Hello, World!" }
end

def returns_proc
  Proc.new { return "Hello, World!" }
end

puts returns_lambda.call # => "Hello, World!"
puts returns_proc.call # => raises a LocalJumpError

If the proc was inside a method, then calling return would be equivalent to returning from that method.

def call_proc
  puts "Before the proc"
  my_proc = Proc.new { return "Inside the proc" }
  my_proc.call
  puts "After the proc"
end

p call_proc
# Prints "Before the proc" and returned value is "Inside the proc"

4. Closures

Different to blocks that must be declared and passed to methods immediately, Ruby procs & lambdas also have another special attribute. When you create a Ruby proc or lambda, it captures the current execution scope with it.

Means that a proc will carry with it values like local variables and methods from the context where it was defined. And it always have the latest version from that context.

def return_proc
    count = 500
    block = Proc.new { puts count }
    count = 501
    block
end

def call_proc(pass_proc)
  pass_proc.call
end

count   = 1
my_proc = return_proc
call_proc(my_proc) # => 501

In conclusion, blocks, procs, and lambdas are all types of closures in Ruby that allow you to pass blocks of code as objects and execute them later. While blocks are anonymous and cannot be stored or returned, procs and lambdas can be stored, passed, and returned.

The key difference between procs and lambdas is the way they handle arguments and the return keyword. Understanding these differences is essential for writing clean, maintainable Ruby code.


© 2016-2024  GinkCode.com