Lesson 6 / 25

Blocks, yield, Procs and Lambdas

Pass behaviour with blocks, write methods that yield, and use procs and lambdas.

Ruby's signature feature

A block is a chunk of code passed to a method, written with { |x| ... } for one-liners or do |x| ... end for multi-line blocks. Methods call their block with yield, check whether one was given with block_given?, or capture it as a parameter with &block. Blocks power iteration (each), resource management (File.open(path) { |f| ... } closes the file automatically), configuration DSLs and callbacks. Short blocks can use numbered parameters (_1) or, since Ruby 3.4, it: prices.map { it * 1.18 }. Blocks can be turned into objects. A Proc (proc { |x| ... } or Proc.new) is lenient about arguments and a return inside it returns from the enclosing method. A lambda (->(x) { x * 2 } or lambda { |x| ... }) checks its argument count strictly and return exits only the lambda, so lambdas behave like small anonymous methods. Both are closures, capturing variables from where they were defined, and are called with .call, .() or []. method(:name) turns an existing method into a callable object.

Writing a method that takes a block

yield, ensure-style cleanup and lambdas as values.

def with_timing(label)
  started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  result = yield                                   # run the caller's block
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
  puts "#{label} took #{(elapsed * 1000).round(1)} ms"
  result
end

total = with_timing("summing") { (1..1_000_000).sum }

def each_batch(items, size)
  return to_enum(:each_batch, items, size) unless block_given?   # enumerator if no block
  items.each_slice(size) { |batch| yield batch }
end

each_batch((1..10).to_a, 4) { |b| puts b.inspect }      # [1, 2, 3, 4] [5, 6, 7, 8] [9, 10]

gst = ->(amount, rate = 0.18) { (amount * (1 + rate)).round(2) }
puts gst.call(100)         # 118.0
puts gst.(100, 0.05)       # 105.0

with_tax = [100, 250].map(&gst)                 # a lambda used as a block
upcased = %w[pen ink].map { it.upcase }         # Ruby 3.4+ 'it' parameter

Handing over a recipe card

A method that yields is a cook who says "I'll prepare the pan and clean up afterwards; you tell me what to fry". The block is the recipe card you hand over at that moment.

Quick check: How does a lambda differ from a proc in Ruby?

  • Lambdas check argument counts strictly and return only from themselves
  • Lambdas cannot capture variables
  • Procs are faster
  • There is no difference
Answer

Lambdas check argument counts strictly and return only from themselves — Lambdas behave like methods; procs are lenient and return from the enclosing method.