Ruby Tips & Tricks #1: Procs with Empty Blocks
Hello and welcome to the first Ruby Tips & Tricks post. In this series I would talk about interesting bits in Ruby.
Let’s start the series with a documented, but little known feature of the Proc.new
constructor.
When Proc.new
is called in a method with attached block, that block will be converted to a Proc
object. Sounds confusing? An example shows it best:
def proc_from
Proc.new
end
proc = proc_from { "hello" }
proc.call #=> "hello"
When I first ran into code like this, it took me a while to digest it, even though this behaviour is noted pretty early in the Proc documentation.
Now that we know that it exists, how do we use it? Is it better than the more conventional example:
def proc_from(&block)
block
end
proc = proc_from { "hello" }
proc.call #=> "hello"
I believe it is! Why? In the conventional example, if there is no block given, block
is nil
. This contributes to the nil problem:
def conventional_proc_from(&block)
block
end
def proc_from
Proc.new
end
conventional_proc_from #=> nil
proc_from #=> ArgumentError: tried to create Proc object without a block
In my book, raising an exemption early is way better than dealing with a nil, introduced a couple of levels down the stack. We could have easily fixed the conventional example with a block_given?
guard and manually raising ArgumentError
, but that would be more convoluted — Proc.new
already does that for us.
While the examples above are pretty trivial, if you need to transform the block of a method, why not get it with Proc.new
?
If you know the difference between Proc.new
, Kernel#proc
and Kernel#lambda
, a fair question to ask yourself is whether you can get a lambda out of the method block? This is where things get really interesting:
def lamba_from
lambda
end
hello = lambda_from { |name| "Hello #{name}" } #=> warning: tried to create Proc object without a block
hello.call #=> ArgumentError: wrong number of arguments (0 for 1)
hello.call("Genadi") #=> "Hello Genadi"
While this is converting the proc block to lambda semantics, it produces a confusing warning — warning: tried to create Proc object without a block
. The warning carries the same message as the ArgumentError
we saw above. It is deliberate and happens only for Kernel#lambda
.
With this, I’m ending the first Ruby Tips & Tricks! Do you find Proc.new
useful? Know why the Ruby core developers decided that Kernel#lambda
should produce a warning? Don’t like this approach at all? Drop a line on twitter at @gsamokovarov.