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

 
82
Kudos
 
82
Kudos

Now read this

Ruby Tips & Tricks #2: Lambda Literals in 1.9.3

Ruby 1.9.3 [support] is coming to an end and with it — an end of a syntax limitation. Welcome to the second Ruby Tips & Tricks post. Have you ever wondered why the [community style guide] suggest you to write lambda literals like... Continue →