首先,让我们稍微清理一下,以便更容易看出出了什么问题:
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' }
所以,n
是10
,块是 { puts 'Take this' }。由于n
既不是1
也不是2
,我们到达yield
,它将控制权转移到块。
现在我们打电话
call_block(n-1)
这是
call_block(9)
请注意,我们不是用块调用它。所以,对于这个新的调用,n
is9
并且没有阻塞。同样,我们跳过前两行并来到yield
.
但是没有阻止yield
,这就是代码在这里爆炸的原因。
解决方案既明显又微妙。显而易见的部分是:问题是我们没有传递一个块,因此解决方案是我们需要传递块。微妙的部分是:我们如何做到这一点?
使 Ruby 块在语法上如此轻量的原因在于它们是匿名的。但是如果块没有名字,我们就不能引用它,如果我们不能引用它,那么我们就不能传递它。
解决这个问题的方法是在 Ruby 中使用另一种结构,它基本上是比块更重的“代码块”概念的抽象:a 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 对它有特殊的支持。如果您&
在参数列表中的参数名称前面放置一个印记,Ruby 会将作为参数传递到对象中的块“打包”Proc
并将其绑定到该名称。相反,如果您&
在参数列表中的参数表达式前面放置一个印记,它会将其“解包”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' }