Problem with Ruby blocks

£可爱£侵袭症+ 提交于 2019-12-06 09:05:46
yfeldblum

You might want to use this line, as Adam Vandenberg hints:

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

First, let's clean that up a bit so that it's easier to see what's going wrong:

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' }

Now let's just trace it through.

We start by calling

call_block(10) { puts 'Take this' }

So, n is 10 and the block is { puts 'Take this' }. Since n is neither 1 nor 2, we arrive at the yield, which transfers control to the block.

Now we are calling

call_block(n-1)

which is

call_block(9)

Notice that we are not calling it with a block. So, for this new call, n is 9 and there is no block. Again, we skip the first two lines and come to the yield.

But there is no block to yield to, and that's why the code blows up here.

The solution is both obvious and subtle. The obvious part is: the problem is that we are not passing a block, thus the solution is we need to pass the block along. The subtle part is: how do we do that?

The thing that makes Ruby blocks so syntactically lightweight, is that they are anonymous. But if the block doesn't have a name, we cannot refer to it, and if we cannot refer to it, then we cannot pass it along.

The solution to this is to use another construct in Ruby, which is basically a more heavyweight abstraction for the idea of "a chunk of code" than a block: 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' })

As you can see, this is a little bit heavier syntactically, but we can give the Proc a name, and thus pass it along to the recursive calls.

However, this pattern is actually common enough that there is special support in Ruby for it. If you put a & sigil in front of a parameter name in a parameter list, Ruby will "package up" a block that is passed as an argument into a Proc object and bind it to that name. And conversely, if you put a & sigil in front of an argument expression in an argument list, it will "unpack" that Proc into a block:

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' }
Sanjay T. Sharma

That's because of the recursive call to the method call_block without passing in a block. One way of doing it would be:

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"}

EDIT: I must admit that the solution posted by Justice seems more logical.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!