tags:

views:

213

answers:

3
def foo
  f = Proc.new { return "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo" 
end

def bar
  b = Proc.new { "return from bar from inside proc" }
  b.call # control leaves bar here
  return "return from bar" 
end

puts foo # prints "return from foo from inside proc" 
puts bar # prints "return from bar"

I thought the return keyword was optional in Ruby and that you are always returning whether you request it or not. Given that, I find it surprising that foo and bar have different output determined by the fact that foo contains an explicit return in Proc f.

Does anyone know why this is the case?

+6  A: 

return inside a block or Proc[1] will return from the method, in which the block or Proc is defined, not from the block itself. In other words: if you use return inside the definition of foo(), you will return from foo, even if it is inside a block or Proc.

To return from a block you can use the next keyword instead. So:

def foo
  f = Proc.new { next "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo" 
end
puts foo # prints "return from foo"

Ruby has three keywords which return from something:

  1. return - returns from the method or lambda in which it is written
  2. next - returns from the block/proc/lambda it is in
  3. break - returns from the method that yields to the block/invokes the proc/lambda it is in

next and break are named the way they are because they are most commonly used with methods like each where returning from the block will cause the iteration to resume with the next element of the collection and returning from each will cause you to break out of the loop.

[1] Note that here "a Proc" means a Proc object created by Proc.new, while "a lambda" means a Proc created with the lambda keyword. In lambdas return behaves like next, for whatever reason.

Also note that the proc keyword, which hasn't been mentioned yet, behaves like lambda in 1.8 and like Proc.new in 1.9.

sepp2k
Edited to also mention the behaviour of lambdas
sepp2k
note, in your example if next is omitted, the behavior remains.
Sam Saffron
btw, I think we both did a great job answering a different question :) as to why, I think only Matz knows, a lot of the stuff around closures violates the principle of least surprise.
Sam Saffron
+3  A: 

This is the semantics for Procs, it is not necessarily the semantics for all blocks. I agree this is a bit confusing. It is there for added flexibility (and perhaps partially cause Ruby has no spec except for its implementation).

The behavior is defined in the Proc implementation. Lambdas behave differently, so if you would like your returns not to exit out of the enclosing method, use lambdas. Or, omit the return keyword from your Proc.

A deep investigation of Rubys closures is here. It is a fantastic expose.

So:

def foo   
  f = Proc.new {
    p2 = Proc.new { return "inner proc"};
    p2.call
    return "proc"
  }
  f.call
  return "foo"
end

def foo2
  result = Proc.new{"proc"}.call
  "foo2 (proc result is: #{result})"
end

def bar
  l = lambda { return "lambda" }
  result = l.call
  return "bar (lambda result is: #{result})"
end

puts foo
# inner proc
puts foo2
# foo (proc result is: proc) 
puts bar
# bar (lambda result is: lambda)
Sam Saffron
"This is the semantics for Procs, it is not necessarily the semantics for all blocks." It is the semantics for Proc instances created by Proc.new as well as all "normal" blocks (i.e. blocks that aren't used together with the `proc` or `lambda` keywords).
sepp2k
True, I could add an example, but I think my example is complicated enough as it.
Sam Saffron
A: 

Think of it this way: Proc.new just create a block of code that is part of the calling function. proc/lambda create an anonymous function that has special bindings. A little code examples will help:

def foo
  f = Proc.new { return "return from foo from inside Proc.new" }
  f.call # control leaves foo here
  return "return from foo" 
end

is equivalent to

def foo
  begin
    return "return from foo from inside begin/end" }
  end

  return "return from foo" 
end

so it is clear that the return will just return from the function 'foo'

in contrast:

def foo
  f = proc { return "return from foo from inside proc" }
  f.call # control stasy in foo here
  return "return from foo" 
end

is equivalent to (ignoring the bindings since not used in this example):

def unonymous_proc
  return "return from foo from inside proc"
end

def foo
  unonymous_proc()
  return "return from foo" 
end

Which is as clearly will not return from foo and continue to the next statement instead.

Vitaly Kushner