diff --git a/proc.c b/proc.c index 1a3f337295..7f4f2d46b6 100644 --- a/proc.c +++ b/proc.c @@ -172,8 +172,10 @@ proc_clone(VALUE self) * call-seq: * prc.lambda? -> true or false * - * Returns +true+ for a Proc object for which argument handling is rigid. - * Such procs are typically generated by +lambda+. + * Returns +true+ if a Proc object is lambda. + * +false+ if non-lambda. + * + * The lambda-ness affects argument handling and the behavior of +return+ and +break+. * * A Proc object generated by +proc+ ignores extra arguments. * @@ -3370,9 +3372,11 @@ rb_method_compose_to_right(VALUE self, VALUE g) * Procs are coming in two flavors: lambda and non-lambda (regular procs). * Differences are: * - * * In lambdas, +return+ means exit from this lambda; - * * In regular procs, +return+ means exit from embracing method + * * In lambdas, +return+ and +break+ means exit from this lambda; + * * In non-lambda procs, +return+ means exit from embracing method * (and will throw +LocalJumpError+ if invoked outside the method); + * * In non-lambda procs, +break+ means exit from the method which the block given for. + * (and will throw +LocalJumpError+ if invoked after the method returns); * * In lambdas, arguments are treated in the same way as in methods: strict, * with +ArgumentError+ for mismatching argument number, * and no additional argument processing; @@ -3383,6 +3387,40 @@ rb_method_compose_to_right(VALUE self, VALUE g) * * Examples: * + * # +return+ in non-lambda proc, +b+, exits +m2+. + * # (The block +{ return }+ is given for +m1+ and embraced by +m2+.) + * $a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { return }; $a << :m2 end; m2; p $a + * #=> [] + * + * # +break+ in non-lambda proc, +b+, exits +m1+. + * # (The block +{ break }+ is given for +m1+ and embraced by +m2+.) + * $a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { break }; $a << :m2 end; m2; p $a + * #=> [:m2] + * + * # +next+ in non-lambda proc, +b+, exits the block. + * # (The block +{ next }+ is given for +m1+ and embraced by +m2+.) + * $a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { next }; $a << :m2 end; m2; p $a + * #=> [:m1, :m2] + * + * # Using +proc+ method changes the behavior as follows because + * # The block is given for +proc+ method and embraced by +m2+. + * $a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { return }); $a << :m2 end; m2; p $a + * #=> [] + * $a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { break }); $a << :m2 end; m2; p $a + * # break from proc-closure (LocalJumpError) + * $a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { next }); $a << :m2 end; m2; p $a + * #=> [:m1, :m2] + * + * # +return+, +break+ and +next+ in the subby lambda exits the block. + * # (+lambda+ method behaves same.) + * # (The block is given for stubby lambda syntax and embraced by +m2+.) + * $a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { return }); $a << :m2 end; m2; p $a + * #=> [:m1, :m2] + * $a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { break }); $a << :m2 end; m2; p $a + * #=> [:m1, :m2] + * $a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { next }); $a << :m2 end; m2; p $a + * #=> [:m1, :m2] + * * p = proc {|x, y| "x=#{x}, y=#{y}" } * p.call(1, 2) #=> "x=1, y=2" * p.call([1, 2]) #=> "x=1, y=2", array deconstructed @@ -3487,6 +3525,29 @@ rb_method_compose_to_right(VALUE self, VALUE g) * {test: 1}.to_proc.call(:test) #=> 1 * %i[test many keys].map(&{test: 1}) #=> [1, nil, nil] * + * == Orphaned Proc + * + * +return+ and +break+ in a block exit a method. + * If a Proc object is generated from the block and the Proc object + * survives until the method is returned, +return+ and +break+ cannot work. + * In such case, +return+ and +break+ raises LocalJumpError. + * A Proc object in such situation is called as orphaned Proc object. + * + * Note that the method to exit is different for +return+ and +break+. + * There is a situation that orphaned for +break+ but not orphaned for +return+. + * + * def m1(&b) b.call end; def m2(); m1 { return } end; m2 # ok + * def m1(&b) b.call end; def m2(); m1 { break } end; m2 # ok + * + * def m1(&b) b end; def m2(); m1 { return }.call end; m2 # ok + * def m1(&b) b end; def m2(); m1 { break }.call end; m2 # LocalJumpError + * + * def m1(&b) b end; def m2(); m1 { return } end; m2.call # LocalJumpError + * def m1(&b) b end; def m2(); m1 { break } end; m2.call # LocalJumpError + * + * Since +return+ and +break+ exists the block itself in lambdas, + * lambdas cannot be orphaned. + * */