In Ruby, blocks, procs, and lambdas are powerful tools for handling chunks of code. They allow you to pass code to methods, store code in variables, and create reusable code snippets. Understanding these concepts is crucial for writing clean, efficient, and flexible Ruby code.

  1. Blocks

What is a Block?

A block is a chunk of code enclosed between do...end or curly braces {...}. Blocks are not objects, but they can be passed to methods and executed within those methods.

Syntax

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

# Using curly braces
[1, 2, 3].each { |number| puts number }

Explanation

  • each is a method that iterates over each element of the array.
  • The block { |number| puts number } is passed to the each method.
  • |number| is a block parameter that takes the value of each element in the array.
  • puts number prints each element.

Practical Example

def greet
  yield "Hello"
  yield "Hi"
end

greet { |greeting| puts "#{greeting}, World!" }
  • yield is used to call the block passed to the method.
  • The block { |greeting| puts "#{greeting}, World!" } is executed twice with different values.

  1. Procs

What is a Proc?

A Proc (short for procedure) is an object that encapsulates a block of code, which can be stored in a variable, passed to methods, and called.

Syntax

my_proc = Proc.new { |name| puts "Hello, #{name}!" }
my_proc.call("Alice")

Explanation

  • Proc.new creates a new Proc object.
  • my_proc.call("Alice") executes the block with the argument "Alice".

Practical Example

def execute_proc(my_proc)
  my_proc.call("Bob")
end

my_proc = Proc.new { |name| puts "Hello, #{name}!" }
execute_proc(my_proc)
  • execute_proc method takes a Proc as an argument and calls it with "Bob".

  1. Lambdas

What is a Lambda?

A lambda is a special type of Proc with stricter argument checking and different behavior for return.

Syntax

my_lambda = lambda { |name| puts "Hello, #{name}!" }
my_lambda.call("Charlie")

Explanation

  • lambda creates a new lambda object.
  • my_lambda.call("Charlie") executes the lambda with the argument "Charlie".

Practical Example

def execute_lambda(my_lambda)
  my_lambda.call("Dave")
end

my_lambda = lambda { |name| puts "Hello, #{name}!" }
execute_lambda(my_lambda)
  • execute_lambda method takes a lambda as an argument and calls it with "Dave".

Differences Between Procs and Lambdas

Feature Proc Lambda
Argument Checking Does not enforce argument count Enforces argument count
Return Behavior Returns from the enclosing method Returns from the lambda itself

Example of Differences

# Argument checking
my_proc = Proc.new { |a, b| puts "Proc: #{a}, #{b}" }
my_proc.call(1) # No error, prints "Proc: 1, "

my_lambda = lambda { |a, b| puts "Lambda: #{a}, #{b}" }
# my_lambda.call(1) # Error: wrong number of arguments (given 1, expected 2)

# Return behavior
def proc_return
  my_proc = Proc.new { return "Proc return" }
  my_proc.call
  "Method return"
end

def lambda_return
  my_lambda = lambda { return "Lambda return" }
  my_lambda.call
  "Method return"
end

puts proc_return   # Prints "Proc return"
puts lambda_return # Prints "Method return"

Exercises

Exercise 1: Using Blocks

Write a method repeat that takes a number and a block, and calls the block that many times.

def repeat(n)
  # Your code here
end

repeat(3) { puts "Hello!" }

Solution

def repeat(n)
  n.times { yield }
end

repeat(3) { puts "Hello!" }

Exercise 2: Creating and Using Procs

Create a Proc that takes a number and prints its square. Pass this Proc to a method that calls it with different numbers.

square_proc = Proc.new { |number| puts number ** 2 }

def call_proc(my_proc, number)
  # Your code here
end

call_proc(square_proc, 4)
call_proc(square_proc, 5)

Solution

square_proc = Proc.new { |number| puts number ** 2 }

def call_proc(my_proc, number)
  my_proc.call(number)
end

call_proc(square_proc, 4)
call_proc(square_proc, 5)

Exercise 3: Creating and Using Lambdas

Create a lambda that takes a string and prints it in uppercase. Pass this lambda to a method that calls it with different strings.

uppercase_lambda = lambda { |str| puts str.upcase }

def call_lambda(my_lambda, str)
  # Your code here
end

call_lambda(uppercase_lambda, "hello")
call_lambda(uppercase_lambda, "world")

Solution

uppercase_lambda = lambda { |str| puts str.upcase }

def call_lambda(my_lambda, str)
  my_lambda.call(str)
end

call_lambda(uppercase_lambda, "hello")
call_lambda(uppercase_lambda, "world")

Conclusion

In this section, you learned about blocks, procs, and lambdas in Ruby. Blocks are chunks of code that can be passed to methods, procs are objects that encapsulate blocks, and lambdas are a special type of proc with stricter argument checking and different return behavior. Understanding these concepts will help you write more flexible and reusable code. In the next section, we will dive into metaprogramming, a powerful feature of Ruby that allows you to write code that writes code.

© Copyright 2024. All rights reserved