Proc.new Trick

Ruby’s Proc.new without an explicit block

Andrew Livingston
Feb 23, 2017 · 3 min read

All Ruby developers know code like this, where a method takes a block, and the unary operator & converts the block into a Proc that can be called with #call:

def random_method(&block)
block.call
end
random_method {"Hello"}# => Hello

Similarly, all Ruby developers know code like this, in which a method can yield to any block that is passed to it:

def random_method_two
yield
end
random_method_two {"Hello"}# => Hello

However, there’s another way to pass and call blocks in methods:

def random_method_three
prok = Proc.new
prok.call
end
random_method_three {"Hello From Three"}

This method looks like it would raise an error because Proc.new must take a block, and when it doesn’t, Ruby raises this error:

Proc.new
# => ArgumentError: tried to create Proc object without a block

But if we run the following code, even though we haven’t explicitly passed a block to Proc.new, Ruby raises no error:

def random_method_three
prok = Proc.new
prok.call
end
random_method_three {"Hello From Three"}# => Hello From Three

What’s happening here? Proc.new will look for a block in its context, find the block we passed into random_method_three, and run Proc.new with that block.

This is useful when you would like to create and use a Proc object inside of a method but would like ‘choose’ when to use the Proc. This avoids the performance overhead of using the &block construction, which automatically converts any block passed to a method into a Proc object regardless of whether you want the Proc to be called in the method.

For example, take these two methods:

def uses_ampersand(trigger, &block)
if trigger && block_given?
block.call
end
end
def doesnt_use_ampersand(trigger)
if trigger && block_given?
a = Proc.new
a.call
end
end

The method uses_ampersand(trigger, &block) will convert any block passed to it into a Proc object regardless of whether trigger is truthy or falsy, while doesnt_use_ampersand(trigger) will only create a Proc object if trigger is truthy. This difference in behavior results in a non-negligible performance difference. We can see this by using Ruby’s benchmark library.

The set up:

require 'benchmark'def uses_ampersand(trigger, &block)
if trigger && block_given?
block.call
end
end
def doesnt_use_ampersand(trigger)
if trigger && block_given?
a = Proc.new
a.call
end
end
n = 4000000Benchmark.bmbm do |test_case|
test_case.report("Uses &block") do
n.times do
uses_ampersand(false) do
"hello from block (won't get called)"
end
end
end
test_case.report("Uses Proc.new") do
n.times do
doesnt_use_ampersand(false) do
"hello from block (won't get called)"
end
end
end
end

When run, we see the performance difference:

$ ruby proc_comparison.rbRehearsal -------------------------------------------------
Uses &block 4.680000 0.200000 4.880000 ( 5.014182)
Uses Proc.new 0.570000 0.000000 0.570000 ( 0.583753)
---------------------------------------- total: 5.450000sec
user system total real
Uses &block 4.500000 0.160000 4.660000 ( 4.721222)
Uses Proc.new 0.590000 0.010000 0.600000 ( 0.669252)

So, we see the method that explicitly takes a block and uses the & operator to convert that block — every time — into a Proc is ~10x slower than the method that selectively uses the block only when trigger is truthy. This can be explained by the fact that uses_ampersand(trigger, &block) will always create a Proc from the block that is passed into it, while doesnt_use_ampersand(trigger) will only sometimes — when we want it to, by passing in a truthy trigger — create a Proc.

I hope you found this helpful.

Andrew Livingston

Written by

I’m an entrepreneur and programmer.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade