开发者

Problem with Ruby blocks

开发者 https://www.devze.com 2023-01-27 03:10 出处:网络
What is wrong in the code? 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)

What is wrong in the code?

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

I am trying to use yield to print Take this other than the tenth fibonacci开发者_Go百科 number.

I am getting the error: in `call_block': no block given (LocalJumpError)

Even the following code throws error:

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


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


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

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


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号