8

The break statement for blocks (as per The Ruby Programming Language) is defined as follows:

it causes the block to return to its iterator and the iterator to return to the method that invoked it.

Therefore when the following code is run, it results in a LocalJumpError.

def test
    puts "entering test method"
    proc = Proc.new { puts "entering proc"; break }
    proc.call # LocalJumpError: iterator has already returned
    puts "exiting test method"
end
test

While the following code does not throw a LocalJumpError. What is special about the ampersand sign? Doesn't the ampersand sign implicitly use Proc.new?

def iterator(&proc)
    puts "entering iterator"
    proc.call # invoke the proc
    puts "exiting iterator" # Never executed if the proc breaks
end

def test
    iterator { puts "entering proc"; break }
end
test

In other words, I read the ampersand sign as a means of in-lining the Proc.new call. At which point the behavior should be just the same as the first code snippet.

def iterator (p = Proc.new { puts "entering proc"; break})
...
end

Disclaimer: I am newb learning the language (ruby 1.9.2), and therefore will appreciate references and a detailed synopsis.

Salman Paracha
  • 1,697
  • 2
  • 16
  • 27

3 Answers3

8

break makes the block and the caller of the block return. In the following code:

proc = Proc.new { break }

The "caller" of the block which is converted to a Proc object is Proc.new. break is supposed to make the caller of the block return, but Proc.new has already returned.

In this code:

def iterator(&b); b.call; end
iterator { break }

The caller of the block is iterator, so it makes iterator return.

Alex D
  • 29,755
  • 7
  • 80
  • 126
  • got it...this is the best answer. However, aren't all blocks converted into actionable code via Proc.new? Hence doesn't iterator (&b) become iterator (b = Proc.new b)? – Salman Paracha Jan 25 '12 at 04:42
  • & and Proc.new are not the same. & is core syntax; Proc.new is a library method. You can write your own Proc.new like this: `class Proc; def self.new(&b); b; end; end`. But there's no way you can implement your own core syntax (aside from hacking the interpreter, or using a preprocessor). – Alex D Jan 25 '12 at 14:05
3

Here's the answer.

Ampersand is used to convert a proc to a block and a block to a proc.

I changed the example so as to relate to your case:

def run_my_code(&my_code)
 puts 'before proc'
 my_code.call
 puts 'after proc'
end
run_my_code { puts "passing a block, accepting a proc"; break}
=> before proc
   passing a block, accepting a proc

As you can see it didn't reach the 'after proc'

def run_my_code
 yield
end
my_proc = Proc.new  { puts "passing a proc instead of block"; break}
run_my_code &my_proc
=> passing a proc instead of block
   LocalJumpError: break from proc-closure
   from (pry):75:in `block in <main>'

In your second example you have a proc in result, the proc breaks from iterator and returns to test function.

def iterator(&proc)
  puts 'entering iterator'
  proc.call
  puts 'exiting iterator'
end

def test
  puts 'before test'
  iterator { puts 'entering proc'; break }
  puts 'after test'
end

=>before test
entering iterator
entering proc
after test
megas
  • 21,401
  • 12
  • 79
  • 130
  • If I have this correct, in the first example the break statement returns from Proc.new (as that's an iterator) and from the block that enclosed it. I documented the definition in the beginning of the question as I understood, why the first one failed. But, if in the second example the ampersand sign is syntactic sugar for Proc.new, then I don't understand, why it doesn't fail there – Salman Paracha Jan 17 '12 at 02:47
  • so, if a block has been converted into a proc, then shouldn't the break statement work similarly as the first code snippet? I mean, if we are basically inlining a Proc.new, why does that behave differently than a Proc.new that was in a separate line above the method call? – Salman Paracha Jan 17 '12 at 22:47
  • 1
    "the proc breaks from iterator and returns to test function." if that is true, then the following definition of iterator shouldn't fail, but does: def iterator (p = Proc.new { puts "entering proc"; break}). All I can think of is that Proc.new is an iterator and the ampersand sign, doesn't use the yield statement to create the proc. – Salman Paracha Jan 18 '12 at 21:39
  • Yes, my explanation is not true. Now I think that's because of 'break' keyword and not of proc. But can't find full description of this keyword. – megas Jan 19 '12 at 05:19
0

It has to do with the difference between blocks, procs and lambdas - and their respective scopes.

I wrote a post about it back in 2009 that you might find useful: http://www.leonardoborges.com/writings/2009/07/22/procs-lambdas-blocks-whats-the-difference/

Hope this helps.

leonardoborges
  • 5,579
  • 1
  • 24
  • 26
  • The post was clearly insightful, but given that I am using a proc in both instances, I am not sure why Ruby is trying to give special treatment to one proc over another? – Salman Paracha Jan 17 '12 at 03:37
  • It has to do with the return keyword explanation in the post. break, such as return in this case means break from the calling method, test in this case. However you can't break from test as you can verify by putting a break right after `Proc.new {...}` Which is different on your second snippet since it'll return from the calling method, iterator in this case, returning control to the new version of test. – leonardoborges Jan 17 '12 at 03:51
  • So if test was wrapped in a method called test_test, the break statement in Proc.new shouldn't fail? Here' how I am reading the block to proc conversion def iterator(&proc); #implicity call proc = Proc.new {proc}; ...end In which case, the LocalJumpError should happen, just like in test. – Salman Paracha Jan 17 '12 at 05:01
  • it does call it implicitly but not at `iterator(&proc)`. The method gets the Proc passed in as opposed to creating it inline. – leonardoborges Jan 17 '12 at 05:12
  • then why does the following fail def iterator(proc) ...end iterator Proc.new { puts "entering proc"; break } – Salman Paracha Jan 17 '12 at 14:28
  • my point being that if the Proc is being passed in to the iterator then, it should behave just as if someone called Proc.new, unless & has been given special privileges – Salman Paracha Jan 17 '12 at 14:29
  • @Salman, look at the answer I just posted. It explains why `iterator(&Proc.new { break })` is different from `iterator { break }`. – Alex D Jan 24 '12 at 22:15