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

3 Answers 3

7

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' }
Sign up to request clarification or add additional context in comments.

4 Comments

Justice's answer indicates that you can "pass it [the block] along" in this case.
+1 for explaining what is happening and how to improve OP's code.
@Andrew Grimm: That's not passing along the block. That's passing two completely new, independent, different blocks, which just happen to call the original block. You can clearly see the difference if you look at the stacktraces. Just force an exception: a = 0; call_block(100){raise if (a+=1) > 10} and you will see that in my case, there is only one block, and the stack is much less deep, whereas in Justice's version, there is a deep stack of blocks on top of the stack of methods. I am also not entirely convinced that all block-relative control flow works properly.
That was a bloody fine answer. Solved my problem and made me understand. Write a book! ;o)
3

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

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

3 Comments

those 'yield' statements inside the otherwise-empty blocks - which blocks are they yielding to? I'm confused, I thought those blocks themselves were called because of a different yield statement inside the call_block method.
yield calls the block or proc passed into the currently-executing method's block/proc argument slot.
To be more precise, yield calls the block or proc passed into the method in the block/proc argument slot, in which method the yield lexically appears.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.