Ruby - What are the differences between checking if block_given? and !block.nil?
Asked Answered
S

3

7

I have a ruby method that needs to check if a block was passed to it. A colleague is suggesting that simply checking if block.nil? is slightly faster in performance and works for named blocks. This is already quite annoying since he is using the named block and invoking it using block.call rather than yield which has been shown to be significantly faster, since named blocks are more easy to understand in terms of readability.

Version 1:

def named_block &block
   if block.nil?
     puts "No block"
   else
     block.call
   end
end

Version 2:

def named_block &block
  if !block_given?
    puts "No block"
  else 
    block.call
  end
end

Benchmarking shows that version 1 is slightly faster than version 2, however a quick look at the source code seems to suggest that block_given? is thread safe.

What are the main differences between the two approaches? Please help me prove him wrong!

Shoshanashoshanna answered 23/11, 2016 at 10:24 Comment(5)
What you are doing is called “premature optimization.” The productivity bottleneck is never related to ruby internals. So pick up any variant of your choice and go with it.Binni
Also, yield might be perfectly used when named block is given instead of explicit block.call.Binni
@mudasobwa It's still good to know the tradeoffs between implementations. Me & singletony work together on our company's infrastructure. We don't specifically care about the performance of each implementation but on the tradeoffs of each implementation.Cheeseburger
@Cheeseburger in Layman’s terms the answer is “there are no tradeoffs; pick up any approach and make sure it’s usage is consistent across the company.” if !block_given? negation under two-branched if, appeared in the OP, is waaaay worse, than cb.nil? vs block_given?.Binni
@mudasobwa actually the invocation in our code is block.call unless block.nil? vs block.call if block_given?, so my post is a variation and I agree it wasn't the best one. That said, I agree with @the_drow, we care about the tradeoffs (and proving I am right) and some of the answers here address those well.Shoshanashoshanna
S
7

First off, while a single nil? check might be faster than block_given?, capturing the block is slow. So unless you were going to capture it anyway, the performance argument is invalid.

Secondly, it's easier to understand. Whenever you see block_given?, you know exactly what is up. When you have x.nil?, you have to stop and think what x is.

Thirdly, it's an idiom. In my experience, the overwhelming majority of Ruby developers will prefer block_given?. When in Rome...

Lastly, you can keep it consistent. If you always use block_given? the problem is solved for you. If you use nil? checks, you have to have the block captured.

  • There is a performance overhead.
  • It's more verbose, something Rubyists try to avoid.
  • Naming things is one of the hardest things in programming. :) Can you think of a good name for the block Enumerable#map will get for example?
  • "Grepability" is a desirable trait for a codebase to have. If you want to find all the places where you check if you were given a block, doing nil? checks can prove difficult.
Sholom answered 23/11, 2016 at 10:46 Comment(2)
@Cheeseburger what about reading ruby documentation on the subject? I voted for the question to be closed as “primarily opinion based,” and I suggest everybody here to do so.Binni
@the_drow, procs are immutable. Also I haven't checked the implementation, but my guess is that a nil? check is atomic. So I don't think it makes a difference either way.Sholom
B
0

I think that the main difference is that block_given? can be used without explicitly defining &block in method definition:

def named_block
  if !block_given?
    puts "No block"
  else 
    yield
  end
end

Which version is better when it goes for readability? Sometimes explicitly naming the block can be more readable and sometimes yield can be more readable. It's also mater of personal preferences.

When it goes to speed, in benchmarks, that you've included, the yield is faster. That is because Ruby doesen't have to initialize new object (Proc) for the block and assign it to variable.

Bill answered 23/11, 2016 at 10:35 Comment(0)
B
0

There is another way to accomplish this:

def named_block
   (Proc.new rescue puts("No block") || ->{}).call
end

▶ named_block
#⇒ No block
▶ named_block { puts 'BLOCK!' }
#⇒ BLOCK!

please don’t take this too seriously

UPD: as noted by @Lukas in comments, it fails on the block, that raises an exception ⇒ FIXED

Binni answered 23/11, 2016 at 10:57 Comment(8)
To downvoters: I won’t remove this answer, I don’t care about reputation and this answer is perfectly valid. If for some reason you think it’s not a proper way to accomplish what’s asked, start with proving it.Binni
I dont thing this is perfectly valid if i raise exception like this named_block { raise ArgumentError, "Test error" } your code produce 'No block' :)Godden
@LukasBaliak indeed: added to answer. Anyway.Binni
@LukasBaliak within your comment downvote would be fine, as a matter of fact :)Binni
@LukasBaliak I fixed an issue, though :)Binni
Nice, but i still prefere to use block_given? ? block.() : 'No Block' :DGodden
I don't think it's a great answer--it's just too clever to be useful--but I did just learn what Proc.new does when not given a block. Thanks for teaching me something.Fogdog
Looks like this is deprecated now, Ruby 2.7.0p0 says warning: Capturing the given block using Proc.new is deprecated; use '&block' insteadSolifidian

© 2022 - 2024 — McMap. All rights reserved.