Why does the break statement in ruby behave differently when using Proc.new v. the ampersand sign?
Asked Answered
B

3

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.

Boscage answered 17/1, 2012 at 2:28 Comment(4)
No time for a proper answer, but this is all about scope, not so much a Proc or Lambda being special.Faker
when you do get time...please visit this question again. I would appreciate your insightBoscage
Instead of Proc.new try lambda.Phony
This is a general question about the language; in implementation I would use a lambdaBoscage
B
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.

Bobsleigh answered 24/1, 2012 at 22:13 Comment(2)
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)?Boscage
& 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).Bobsleigh
L
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
Lyn answered 17/1, 2012 at 2:43 Comment(4)
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 thereBoscage
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?Boscage
"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.Boscage
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.Lyn
F
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.

Fen answered 17/1, 2012 at 3:22 Comment(7)
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?Boscage
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.Fen
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.Boscage
it does call it implicitly but not at iterator(&proc). The method gets the Proc passed in as opposed to creating it inline.Fen
then why does the following fail def iterator(proc) ...end iterator Proc.new { puts "entering proc"; break }Boscage
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 privilegesBoscage
@Salman, look at the answer I just posted. It explains why iterator(&Proc.new { break }) is different from iterator { break }.Bobsleigh

© 2022 - 2024 — McMap. All rights reserved.