Overview
This post discusses Ruby's block syntax.
What is Block Syntax?
A syntax that allows you to pass code snippets to other methods, enabling those methods to execute the code within.
Defined using do..end or {}. Blocks can accept arguments.
# do..end
[1...3].each do |number|
puts number
end
# {}
[1...3].each { |number| puts number}
You can also define methods that accept blocks.
def greet
puts "Hi"
# Execute the passed block
yield if block_given? # block_given? checks if a block was passed
puts "Bye"
end
# Passing a block
greet do
puts "Hello"
end
# Output:
# Hi
# Hello
# Bye
Additionally, you can pass blocks as method arguments using &.
def greet(&block)
puts "Hi"
block.call if block
puts "Bye"
end
greet do
puts "Hello"
end
# Output:
# Hi
# Hello
# Bye
Objects with a to_proc method can also be passed as blocks.
class Greeting
def to_proc
Proc.new { |name| puts "Hello, #{name}!" }
end
end
%w(Alice Bob Charlie).each(&Greeting.new)
# Output:
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!
Blocks are closures, just like Proc and lambda.
def count(&block)
puts block.call
return block
end
x = 0
b = count do
x + 1
end # =>1
x += 10
puts b.call # =>11
do..end and {} have different precedence, with {} having higher precedence.
array = [1, 2, 3]
# Low precedence, so the block is evaluated before being passed to map (it gets passed to pp instead)
pp array.map do |x|
x * 2
end
# Output:
#<Enumerator: ...>
# High precedence, so the block is passed to map and evaluated
pp array.map { |x| x * 2 } # => [2, 4, 6]
You can replace a block with a symbol only when calling a single method within the block. Symbols have the to_proc method (Symbol#to_proc) implemented.
# Normal form
numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select { |n| n.even? }
p even_numbers.inspect # => [2, 4]
# Using a symbol
numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select(&:even?)
p even_numbers.inspect # => [2, 4]
# Using a symbol with a custom method
module Numbers
def my_even?
self % 2 == 0
end
end
class Integer
include Numbers
end
puts [1, 2, 3].select(&:my_even?) # => [2]
As for the distinction between do..end and {}, consider the following:
- Use
do..endfor multiple lines - Use
{}for single lines - Use
{}when used as a return value - Use
do..endwhen passing procedures
Since this can also depend on coding conventions, it's best to consider this as just one example.
References
- docs.ruby-lang.org - Method Calls (super, with blocks, yield)
- style.potepan.com - How to Use Block Syntax in Ruby (do..end)
- obelisk.hatenablog.com - Ruby Blocks are Closures
- qiita.com - If You Don't Fully Understand Block Processing in Ruby, Take a Look Without Getting Angry
- zenn.dev - 【Ruby】 Got Stuck on the Difference Between do...end and {...}
- mickey24.hatenablog.com - Distinguishing Ruby Block Syntax (do end, {})
- Understanding Ruby Blocks Properly
- active.nikkeibp.co.jp - Yukihiro Matsumoto, Ruby Developer, Deeply Explains the Concept of Block Syntax
musclecoding.com - A Detailed Explanation of Ruby Blocks for Beginners! What are Block Variables?