7

我有一个 ruby​​ 方法需要检查一个块是否传递给它。一位同事建议简单地检查是否block.nil?在性能上稍快一些,并且适用于命名块。这已经很烦人了,因为他正在使用命名块并使用它来调用它,block.call而不是yield已经证明明显更快,因为命名块在可读性方面更容易理解。

版本 1:

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

版本 2:

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

基准测试显示版本 1 比版本 2 稍快,但是快速查看源代码似乎表明它block_given?是线程安全的。

这两种方法的主要区别是什么?请帮我证明他错了!

4

3 回答 3

6

首先,虽然单次nil?检查可能比 快block_given?,但捕获块很慢。因此,除非您无论如何都要捕获它,否则性能参数是无效的。

其次,更容易理解。每当您看到block_given?时,您就知道发生了什么。当你有 时x.nil?,你必须停下来想想是什么x

三是成语。根据我的经验,绝大多数 Ruby 开发人员会更喜欢block_given?. 在罗马的时候...

最后,您可以保持一致。如果你经常使用block_given?问题就为你解决了。如果您使用nil?检查,则必须捕获该块。

  • 存在性能开销。
  • 它更冗长,Ruby 主义者试图避免这种情况。
  • 命名事物是编程中最难的事情之一。:) 你能想出一个好名字为块Enumerable#map会得到的例子吗?
  • “Grepability”是代码库所具有的理想特性。如果您想找到所有检查您是否被阻止的地方,那么进行nil?检查可能会很困难。
于 2016-11-23T10:46:17.843 回答
0

我认为主要区别在于无需在方法定义中block_given?明确定义即可使用:&block

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

在可读性方面哪个版本更好?有时明确地命名块可以更具可读性,有时yield可以更具可读性。这也是个人喜好的问题。

当它达到速度时,在您包含的基准测试中,yield速度更快。这是因为 Ruby 不必为Proc块初始化新对象 ( ) 并将其分配给变量。

于 2016-11-23T10:35:40.487 回答
0

还有另一种方法可以做到这一点:

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

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

请不要太认真

UPD:正如@Lukas 在评论中指出的那样,它在块上失败,这会引发异常⇒已修复

于 2016-11-23T10:57:52.523 回答