Ruby块的问题

代码有什么问题?

def call_block(n) if n==1 return 0 elsif n== 2 return 1 else yield return call_block(n-1) + call_block(n-2) end end puts call_block(10) {puts "Take this"} 

我正在尝试使用yield来打印除了第十个斐波纳契数之外。

我收到错误:在`call_block’中:没有给出块(LocalJumpError)

即使以下代码抛出错误:

 def call_block(n) if n==1 yield return 0 elsif n== 2 yield return 1 else yield return call_block(n-1) + call_block(n-2) end end puts call_block(10) {puts "Take this"} 

你可能想要使用这一行,正如Adam Vandenberg暗示的那样:

 return call_block(n-1) { yield } + call_block(n-2) { yield } 

首先,让我们清理一下,以便更容易看出出现了什么问题:

 def call_block(n) return 0 if n == 1 return 1 if n == 2 yield call_block(n-1) + call_block(n-2) end puts call_block(10) { puts 'Take this' } 

现在让我们来看看它。

我们先打电话来

 call_block(10) { puts 'Take this' } 

所以, n10 ,块是{puts’Take this’}。 由于n既不是1也不是2 ,我们得到了yield ,它将控制转移到块。

现在我们打电话

 call_block(n-1) 

是的

 call_block(9) 

请注意,我们没有用块调用它。 所以,对于这个新的调用, n9并且没有阻塞。 再次,我们跳过前两行并达到yield

但是没有任何障碍可以yield ,这就是为什么代码在这里爆炸了。

解决方案既明显又微妙。 显而易见的部分是:问题是我们没有传递一个块,因此解决方案是我们需要传递块。 细微之处在于:我们如何做到这一点?

使Ruby块语法轻量化的原因是它们是匿名的。 但是如果块没有名称,我们就不能引用它,如果我们不能引用它,那么我们就无法传递它。

对此的解决方案是在Ruby中使用另一个构造,这对于“一块代码”而不是块来说基本上是一个更重量级的抽象: Proc

 def call_block(n, blk) return 0 if n == 1 return 1 if n == 2 blk.() call_block(n-1, blk) + call_block(n-2, blk) end puts call_block(10, ->{ puts 'Take this' }) 

正如你所看到的,这语法上有点重,但我们可以给Proc一个名字,然后将它传递给递归调用。

但是,这种模式实际上很常见,因此Ruby中有特殊的支持。 如果在参数列表中的参数名称前放置一个& sigil,Ruby将“打包”一个作为参数传递给Proc对象的块并将其绑定到该名称。 相反,如果你在参数列表中的参数表达式前加上一个& sigil,它会将Proc解压缩到一个块中:

 def call_block(n, &blk) return 0 if n == 1 return 1 if n == 2 yield # or `blk.()`, whichever you prefer call_block(n-1, &blk) + call_block(n-2, &blk) end puts call_block(10) { puts 'Take this' } 

这是因为在不传入块的情况下对方法call_block进行递归调用。 一种方法是:

 def call_block(n, &blk) if n == 1 return 0 elsif n == 2 return 1 else blk.call() return call_block(n-1, &blk) + call_block(n-2, &blk) end end puts call_block(4) {puts "Take this"} 

编辑:我必须承认司法公布的解决方案似乎更符合逻辑。