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][example] 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].
[example]: http://www.ruby-doc.org/core-2.1.1/Proc.html#new-method
[Proc]: http://www.ruby-doc.org/core-2.1.1/Proc.html
[the nil problem]: https://www.destroyallsoftware.com/screencasts/catalog/how-and-why-to-avoid-nil
[difference between]: http://batsov.com/articles/2014/02/04/the-elements-of-style-in-ruby-number-12-proc-vs-proc-dot-new/
[deliberate]: https://github.com/ruby/ruby/blob/fb8f7259c3435ac01ff9ac2718d325e7c309336b/proc.c#L626-L628
[@gsamokovarov]: https://twitter.com/gsamokovarov