Why does explicit return make a difference in a Proc?
Ruby has three constructs:
- A block is not an object and is created by
{
...}
ordo
...end
. - A proc is a
Proc
object created byProc.new
orproc
. - A lambda is a
Proc
created bylambda
(orproc
in Ruby 1.8).
Ruby has three keywords that return from something:
-
return
terminates the method or lambda it is in. -
next
terminates the block, proc, or lambda it is in. -
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"
This is the semantics for Proc
s; 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. Lambda
s behave differently, so if you would like your return
s 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)
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.