Tutorial: Blocks in Ruby

Blocks in Ruby are anonymous chunks of code that can be passed to methods as arguments.

They are a powerful feature of Ruby, allowing for concise and flexible code execution.

What You’ll Learn

1. What Are Blocks?

A block in Ruby is a piece of code enclosed between do…end or curly braces {}. Blocks are often used to specify behaviors for methods or to execute code in a specific context.

Example

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

2. Using Blocks with Methods

Many Ruby methods, like each, map, and select, are designed to take blocks to define how they should behave.

Example: Iterating with a Block

[1, 2, 3].each { |num| puts num }
# Output:
# 1
# 2
# 3

3. Block Syntax

3.1 do…end for Multiline Blocks

[1, 2, 3].each do |num|
  puts num * 2
end
# Output:
# 2
# 4
# 6

3.2 Curly Braces {} for Single-Line Blocks

[1, 2, 3].each { |num| puts num * 2 }
# Output:
# 2
# 4
# 6

4. Passing Blocks to Methods

Ruby allows you to write methods that take a block. Use the yield keyword to execute the block within the method.

5. Yielding to a Block

The yield keyword is used within a method to execute the block passed to it.

Example: Basic yield

def greet
  puts "Hello"
  yield
  puts "Goodbye"
end

greet { puts "Ruby!" }
# Output:
# Hello
# Ruby!
# Goodbye

Example: yield with Parameters

def calculate(a, b)
  yield(a, b)
end

result = calculate(5, 3) { |x, y| x + y }
puts result  # Output: 8

6. Block Parameters

Blocks can accept parameters, which are defined between vertical bars (| |).

Example

def iterate_over(numbers)
  numbers.each { |num| yield num }
end

iterate_over([1, 2, 3]) { |n| puts n * 2 }
# Output:
# 2
# 4
# 6

7. Using Proc and lambda

7.1 Proc

A Proc is an object that encapsulates a block.

Example: Creating a Proc

my_proc = Proc.new { puts "Hello from Proc!" }
my_proc.call
# Output: Hello from Proc!

Example: Passing a Proc to a Method

def execute_proc(proc)
  proc.call
end

my_proc = Proc.new { puts "Hello again!" }
execute_proc(my_proc)
# Output: Hello again!

7.2 lambda

A lambda is a special type of Proc that is more strict about arguments.

Example: Creating a lambda

my_lambda = lambda { |x| puts x * 2 }
my_lambda.call(5)  # Output: 10

Differences Between Proc and lambda

  1. A lambda checks the number of arguments passed, while a Proc does not.
  2. A lambda returns control to the caller after execution, but a Proc can return from the method containing it.
def test_proc
  p = Proc.new { return "Proc Exited" }
  p.call
  "After Proc"
end

def test_lambda
  l = lambda { return "Lambda Executed" }
  l.call
  "After Lambda"
end

puts test_proc     # Output: Proc Exited
puts test_lambda   # Output: After Lambda

8. Practical Examples

8.1 Using a Block to Customize Behavior

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

repeat(3) { puts "Ruby is fun!" }
# Output:
# Ruby is fun!
# Ruby is fun!
# Ruby is fun!

8.2 Filtering Elements with Blocks

numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select { |num| num.even? }
puts even_numbers.inspect  # Output: [2, 4]

8.3 Using Blocks with Hashes

scores = { Alice: 90, Bob: 85, Carol: 95 }

scores.each do |name, score|
  puts "#{name}: #{score}"
end
# Output:
# Alice: 90
# Bob: 85
# Carol: 95

8.4 Block for Repeated Tasks

def measure_time
  start_time = Time.now
  yield
  end_time = Time.now
  puts "Elapsed time: #{end_time - start_time} seconds"
end

measure_time do
  sum = 0
  (1..1_000_000).each { |i| sum += i }
end
# Output: Elapsed time: X seconds (depends on your machine)

8.5 Passing Multiple Blocks Using Proc

def execute_blocks(block1, block2)
  block1.call
  block2.call
end

block1 = Proc.new { puts "Block 1 executed!" }
block2 = Proc.new { puts "Block 2 executed!" }

execute_blocks(block1, block2)
# Output:
# Block 1 executed!
# Block 2 executed!

9. Best Practices for Using Blocks

  1. Use Blocks for Customizable Behavior:
    • Blocks are ideal for methods like each, map, or custom iterators.
  2. Leverage Proc and lambda for Reusability:
    • Use Proc and lambda when you need to reuse blocks.
  3. Prefer yield for Simplicity:
    • If a block is required, yield is simple and clear.
  4. Handle Argument Mismatches:
    • Ensure block arguments match the method’s expectations, especially for lambda.

10. Summary

Key Concepts

  • Blocks are anonymous code chunks passed to methods.
  • Use yield to execute blocks.
  • Proc and lambda encapsulate reusable blocks.
  • Blocks are commonly used for iterating and customizing behavior.

Examples in Context

Ruby blocks empower developers to write concise, reusable, and dynamic code, making them essential for Ruby programming.

Related posts

Tutorial: Methods in Ruby

Tutorial: if…else Statements in Ruby

Tutorial: Loops in Ruby