Why does explicit return make a difference in a Proc?
Asked Answered
E

3

61
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?

Eaglewood answered 16/9, 2009 at 22:3 Comment(0)
C
105

Ruby has three constructs:

  1. A block is not an object and is created by { ... } or do ... end.
  2. A proc is a Proc object created by Proc.new or proc.
  3. A lambda is a Proc created by lambda (or proc in Ruby 1.8).

Ruby has three keywords that return from something:

  1. return terminates the method or lambda it is in.
  2. next terminates the block, proc, or lambda it is in.
  3. break terminates the method that yielded to the block or invoked the proc or lambda it is in.

In lambdas, return behaves like next, for whatever reason. next and break are named the way they are because they are most commonly used with methods like each, where terminating the block will cause the iteration to resume with the next element of the collection, and terminating each will cause you to break out of the loop.


If you use return inside the definition of foo, you will return from foo, even if it is inside a block or a proc. To return from a block, you can use the next keyword instead.
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"
Curia answered 16/9, 2009 at 22:13 Comment(5)
Edited to also mention the behaviour of lambdasCuria
note, in your example if next is omitted, the behavior remains.Blackstock
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.Blackstock
Thanks for the thorough answer here. Really clears things up.Prudhoe
Thanks for the explanation with nice exampleCovington
B
16

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 exposé.

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) 
Blackstock answered 16/9, 2009 at 23:28 Comment(4)
"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).Curia
True, I could add an example, but I think my example is complicated enough as it.Blackstock
for some reason lambdas converted to blocks act like they always were blocks: [1].map &lambda{return "lambda"} returns from the function. Is this a bug in JRuby?Apostate
puts foo2 will return foo2 (proc result is: proc) Malfeasance
S
8

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.

Shaped answered 17/9, 2009 at 18:29 Comment(1)
even though this is an old question, please do note that there is a difference between Ruby 1.8.7 and 1.9.3 where in the latter Kernel.proc behaves like Proc.new instead of as lambda.Entertaining

© 2022 - 2024 — McMap. All rights reserved.