Ruby Blocks, Procs & Lambdas
Blocks
Block syntax — do/end vs braces
# Brace style (single-line convention)
[80, 443, 8080].each { |port| puts port }
# do/end style (multi-line convention)
[80, 443, 8080].each do |port|
result = scan(port)
puts "#{port}: #{result}"
end
# Block with multiple parameters
{ web: 443, ssh: 22 }.each do |service, port|
puts "#{service} => #{port}"
end
Yielding to blocks
# Method that yields
def with_retry(max = 3)
attempt = 0
begin
attempt += 1
yield attempt
rescue StandardError => e
retry if attempt < max
raise e
end
end
with_retry(5) do |n|
puts "Attempt #{n}"
connect_to_server
end
# Check if block given
def log(message)
output = "[#{Time.now}] #{message}"
if block_given?
yield output
else
puts output
end
end
log("connected") { |msg| File.write("app.log", msg, mode: "a") }
Procs
Proc creation and usage
# Proc.new
formatter = Proc.new { |msg| "[INFO] #{msg}" }
puts formatter.call("server started")
puts formatter.("also works")
puts formatter["bracket syntax"]
# proc method (lowercase)
square = proc { |x| x ** 2 }
[1, 2, 3].map(&square) # => [1, 4, 9]
# Proc characteristics:
# - Lenient arity (extra args ignored, missing args = nil)
# - return exits enclosing method
p = proc { |a, b| a }
p.call(1) # => 1 (b is nil, no error)
p.call(1, 2, 3) # => 1 (extra arg ignored)
Converting methods to procs
# Symbol#to_proc with &
names = ["vault", "ISE", "bind"]
puts names.map(&:upcase) # => ["VAULT", "ISE", "BIND"]
puts names.select(&:ascii_only?)
# Method reference
method_ref = method(:puts)
[1, 2, 3].each(&method_ref)
Lambdas
Lambda creation and behavior
# Stabby lambda
greet = ->(name) { "Hello, #{name}" }
puts greet.call("Evan")
puts greet.("Evan")
# Multi-line stabby lambda
process = ->(host, port) do
puts "Connecting to #{host}:#{port}"
TCPSocket.new(host, port)
end
# Lambda vs Proc differences:
# 1. Lambdas enforce argument count
lam = ->(a, b) { a + b }
lam.call(1, 2) # => 3
# lam.call(1) # ArgumentError
# 2. Lambda return goes to caller; Proc return exits enclosing method
def test_lambda
lam = -> { return "from lambda" }
lam.call
"after lambda" # this executes
end
def test_proc
prc = proc { return "from proc" }
prc.call
"after proc" # never reached
end
# 3. Check type
lam = -> {}
lam.lambda? # => true
Closures
Blocks, procs, and lambdas are all closures
# They capture the enclosing scope
def make_counter(start = 0)
count = start
incrementer = -> { count += 1; count }
getter = -> { count }
[incrementer, getter]
end
inc, get = make_counter(10)
inc.call # => 11
inc.call # => 12
get.call # => 12
# Practical: config builder
def configure(defaults = {})
config = defaults.dup
yield config
config.freeze
end
settings = configure(retries: 3) do |c|
c[:timeout] = 30
c[:host] = "10.50.1.20"
end