&blkin a parameter list turns a block into a Proc;&procat a call turns a Proc back into a block.
1. A block is syntax, not an object
A block is the { ... } or do ... end you attach to a method call. It is not a value you can hold by itself.
You can't store a block in a variable — Ruby won't even parse it:
b = { |x| x * 2 } # SyntaxError
# Ruby reads { } here as a hash, not a block.
A method can carry at most one block:
[1, 2, 3].each { |x| puts x } # fine — exactly one block
# There is no syntax to attach a second block to the same call.
Inside a method you reach the block with yield:
def run
yield # runs whatever block was attached
yield # you can run it again
end
run { puts "hi" } # prints: hi hi
A block is not a normal argument. It rides along separately from the things in the parentheses:
def how_many(*args)
args.size
end
how_many(1, 2) { puts "ignored" } # => 2 (the block is NOT counted as an argument)
2. A Proc is an object
A Proc is a real object that holds a chunk of code. You can do anything with it that you do with any object.
Store it in a variable:
doubler = proc { |x| x * 2 } # or Proc.new { |x| x * 2 }
doubler.class # => Proc
Call it (three equivalent ways):
doubler.call(5) # => 10
doubler.(5) # => 10 (.() shorthand)
doubler[5] # => 10 ([] shorthand)
Put it in an array and pass it around:
jobs = [proc { puts "a" }, proc { puts "b" }]
jobs.each { |job| job.call } # prints: a b
So the difference is simple: a block is loose syntax attached to one call; a Proc is an object you can keep.
3. & is the bridge between them
& converts a block into a Proc, or a Proc into a block. Which direction happens depends only on where you write the &.
3.a) & in the parameter list → block becomes a Proc
When you write def foo(&blk), the & catches the block the caller attached and gives it to you as a Proc object named blk:
def foo(&blk)
blk.class # => Proc (now it's a real object)
blk.call(10) # => 20 (call it like any object)
end
foo { |x| x * 2 } # the { } block arrives as the Proc `blk`
Without & you'd have no name for the block — you could only use yield. With &blk you can store it, inspect it, or pass it on.
def run
yield # the ONLY way to reach the block
# blk — there's no such variable to refer to
# @saved = blk — can't store it
# blk.arity — can't inspect it
# other(&blk) — can't pass it on
end
run { puts "hi" } # prints: hi
3.b) & at a call site → Proc becomes a block
When you already have a Proc in a variable and want a method to use it as its block, put & in front:
doubler = ->(x) { x * 2 }
[1, 2, 3].map(&doubler) # => [2, 4, 6]
Here doubler is lambda
Without the &, it's treated as a normal argument and map won't use it as the block:
[1, 2, 3].map(doubler) # ArgumentError
# wrong number of arguments (given 1, expected 0)
lambda is a Proc
The arrow ->(x) { ... } just creates a Proc object that happens to be the "strict" kind. You can check:
doubler = ->(x) { x * 2 }
doubler.class # => Proc
doubler.lambda? # => true
So when the text says "a Proc in a variable", a lambda counts — it is a Proc. The word Proc (capital P) is the class; both ways of making one produce an object of that class:
a = ->(x) { x * 2 } # lambda
b = lambda { |x| x * 2 } # lambda (same as above)
c = proc { |x| x * 2 } # plain proc
d = Proc.new { |x| x * 2 }# plain proc
[a, b, c, d].map(&:class) # => [Proc, Proc, Proc, Proc]
[a, b, c, d].map(&:lambda?) # => [true, true, false, false]
3.c) Both directions in one method
Capture a block as a Proc, then forward it as a block to another method. Same &, opposite jobs, decided purely by position:
def logged(&blk) # & in params: block → Proc `blk`
puts "start"
[1, 2, 3].each(&blk) # & at call: Proc `blk` → block for each
puts "end"
end
logged { |n| puts n } # prints: start 1 2 3 end
4. The to_proc rule
One extra rule explains everything else: at a call site, if the thing after & is not already a Proc, Ruby first calls .to_proc on it, then turns the result into a block.
A Symbol knows how to become a Proc:
:upcase.to_proc.call("hi") # => "HI"
%w[a b c].map(&:upcase) # `&` runs `:upcase.to_proc`, then uses it as the block
%w[a b c].map { |s| s.upcase } # exactly the same thing
# both => ["A", "B", "C"]
So &:upcase is roughly the same as &->(x) { x.upcase }.
A Method object does too (remember: method(:name) returns an object):
method(:puts).class # => Method
method(:puts).to_proc.call("hi") # prints: hi
[1, 2, 3].each(&method(:puts)) # `&` runs the Method's `to_proc`, then uses it
[1, 2, 3].each { |x| puts x } # exactly the same thing
# both print: 1 2 3
Even your own objects work after &, as long as they define to_proc:
class Multiplier
def initialize(by) = @by = by
def to_proc = proc { |x| x * @by }
end
triple = Multiplier.new(3)
[1, 2, 3].map(&triple) # => [3, 6, 9]
5. The whole thing in two lines
-
&Xat a call means: takeX, call.to_procif it isn't already a Proc, then use it as the block. -
&blkin a parameter list does the reverse — it catches the block and hands it back as a Proc you can hold.
Top comments (0)