Blocks, Procs, and Lambdas
Ruby's three flavors of passable code, and when a chunk of logic is a block versus a proc versus a lambda.
3 min read
You've already used blocks — { |n| puts n } in 5.times { ... } is one. This lesson covers what a block actually is, and the two ways to turn one into an object you can store and pass around.
Blocks: anonymous chunks of code
A block is code attached to a method call, written with { } or do...end, and passed implicitly rather than as a normal argument:
[1, 2, 3].each { |n| puts n * 2 }
[1, 2, 3].each do |n|
puts n * 2
endAny method can accept a block and run it with yield:
def repeat_twice
yield
yield
end
repeat_twice { puts "Hi!" }
# Hi!
# Hi!yield can also pass values into the block, and block_given? lets a method check whether a block was actually passed before trying to yield to it:
def process(value)
if block_given?
yield(value)
else
value
end
end
process(5) { |n| n * 10 } # 50
process(5) # 5Procs: blocks you can store
A block only exists at the call site — you can't save it in a variable. A Proc is a block turned into a real object:
multiplier = Proc.new { |n| n * 3 }
multiplier.call(5) # 15
multiplier.(5) # 15 -- shorthand
multiplier[5] # 15 -- another shorthandBecause a Proc is an object, you can pass it around, store it in a hash, return it from a method — all things a bare block can't do.
Lambdas: stricter procs
A lambda is a Proc with two important differences:
strict_multiplier = lambda { |n| n * 3 }
strict_multiplier = ->(n) { n * 3 } # the "stabby lambda" syntax, more common
strict_multiplier.call(5) # 15Argument checking. A lambda raises an ArgumentError if you call it with the wrong number of arguments; a Proc silently ignores extras or fills missing ones with nil.
p = Proc.new { |a, b| [a, b] }
p.call(1) # [1, nil] -- no error
l = ->(a, b) { [a, b] }
l.call(1) # ArgumentError: wrong number of argumentsReturn behavior. return inside a lambda exits just the lambda; return inside a Proc tries to return from the enclosing method, which can cause confusing errors if that method has already finished.
def lambda_test
l = -> { return 10 }
l.call
20
end
lambda_test # 20 -- the lambda's return only exits the lambda
def proc_test
p = Proc.new { return 10 }
p.call
20
end
proc_test # 10 -- the proc's return exits proc_test entirelyPassing a block explicitly with &
A method parameter prefixed with & captures the block as a Proc object instead of using implicit yield:
def apply(value, &operation)
operation.call(value)
end
apply(5) { |n| n * 2 } # 10The reverse also works: & turns an existing Proc, lambda, or symbol into a block when calling a method — which is exactly what's happening in the common map(&:to_s) idiom, converting the :to_s symbol into a block that calls .to_s on each element.
For everyday iteration, plain blocks (each, map, select) are what you'll write constantly. Procs and lambdas matter once you need to store logic and use it later, or pass a fully-formed chunk of behavior into multiple methods.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.