[% setvar title Short-circuiting built-in functions and user-defined subroutines %]
Note: these documents may be out of date. Do not use as reference! |
To see what is currently happening visit http://www.perl6.org/
Short-circuiting built-in functions and user-defined subroutines
Maintainer: Garrett Goebel <garrett@scriptpro.com> Date: 6 Sep 2000 Last Modified: 15 Sep 2000 Mailing List: perl6-language@perl.org Number: 199 Version: 4 Status: Frozen
Allow built-in functions and user defined subroutines to catch exceptions and act upon them, particularly those emanating from control blocks. Put another way, allow blocks to simultaneously exit early with a value.
Currently there is not a practical and flexible way to short-circuit
built-in functions like grep
and map
which take blocks as a parameter.
A prime example that was raised is:
my $found = grep { $_ == 1 } (1..1_000_000);
Under Perl 5, one would need to write something like:
eval { grep { $_ ==1 and die "$_\n" } (1..1_000_000) }; chomp(my $found = $@);
Under this current proposal this might be written as:
my $found = grep { $_ == 1 and return $_ && last grep } (1..1_000_000); (or alternatively) my $found = grep { $_ == 1 and abort grep $_ } (1..1_000_000);
It is the opinion of the author that while the proposal saves little by way of typing, that it is minimally an improvement in clarity. Further this proposal introduces semantics which will make other tasks easier.
reject the current element and continue accept the current element and continue reject the current element and abort accept the current element and abort reject the current element and retry accept the current element and retry
How do you do that *and* be consistent with existing semantics?
There has been much discussion and ideas exchanged on the best way to
achieve this. Discussion ranging from specific solutions which would
only apply to the grep
and map
built-ins, to more general ones
such as unifying the exception syntax for loop, code, and bare blocks.
There has also been a vocal contingent protesting that all of the
suggestions attempt to shoehorn too much new functionality into
existing semantics and that no one has yet presented any workable
solutions.
The current ideas tend to converge around the following three options:
Grant code blocks their names as labels, and add new keywords
for code block control: continue
/abort
/retry
:
# short-circuit after first acceptance... $found = grep { ($_ == 1) and abort grep $_ } (1..1_000_000); # short-circuit after first rejection... $found = grep { ($_ == 1) and abort grep } (1..1_000_000);
Grant code blocks their own names as labels, and the ability to catch next/last/redo exceptions which explicitly use those labels.
# short-circuit after first acceptance... @smalls = grep { ($_ == 1) and return $_ && last grep } (1..1_000_000); # short-circuit after first rejection... @smalls = grep { ($_ == 1) and last grep } (1..1_000_000);
The built-in function's operation should catch exceptions thrown from the block, where the exception thrown is true or false (alternative: 1 or false). Example:
# short-circuit after first acceptance... $found = grep { ($_ == 1) and die 1 } (1..1_000_000); # short-circuit after first rejection... $found = grep { ($_ == 1) or die 0 } (1..1_000_000);
Detractors would argue:
First Proposal: Add code labels and new keywords: continue
/abort
/retry
Second Proposal: Add code labels, and let code blocks use
next
/last
/redo
syntax which explicitly use those labels.
Third Proposal: Extend use of die
die
into
something that can be automatically caught by built-ins? I.e., no good
support for multi-level exceptions.next
/last
/redo
control as is available with loop blocks.The author shares much of detractors opinions for the third proposal, and would prefer either the first or second to be implemented. The reason I prefer one of these two is that I believe they are more intuitive, flexible, and semantically rich. As such, the remainder of this RFC concern itself with issues pertaining to the first two proposals.
It might therefore follow that it would be good to define goto foo
to
have the same semantics as goto &foo
when the label referred to is a
function. Namely to pass @_. It would then be possible if desired to
deprecate goto &foo
.
caller
. 0 refers to the current code block,
1 the caller's, and so forth. Otherwise undef
may also be used to refer
to the current code block.Regardless of whether or not this proposal is implemented, it might be nice to add the same numeric label semantics when labels are used with loop control statements.
Examples:
sub func1 { @_ ? &func2 : abort undef, 0 } sub func2 { $_[0] eq undef ? abort func1, 0 : &func3 } sub func3 { $_[0] eq 'foo' ? abort 2, 0 : 1 }
sub foo { # Code Block eval { # Bare Block for (...) { # Loop Block last && return 1; # Note: all die && return 1; # of these go abort 1, 1 # to different return 1; # places } }; }
This example doesn't confuse the author. But that isn't to say that others won't find it confusing.
return $value && next LABEL
be used to support both short-circuiting
and returning a valueThere was some consensus that return
is a good fit for returning a value
from a control block. But it doesn't support multi-level escaping. Therefore
the use of loop control syntax is tacked on. The question becomes if both
return
and last
are both exceptions which exit the block early, how
do you catch them both and handle them appropriately? Is &&
sufficient
to the cause? From the author's perspective it looks fine and dandy. But I
have little or no idea how this would work in regard to Perl internals.
die
&& return
$valueIf loop control syntax is grafted on for use with code blocks, then a Perl 5 -> 6 translator would need to explicitly label all loop blocks and loop control statements.
I haven't a clue.
Too many contributors to note them all down. The content of this RFC owes heavily to list discussions. The author has simply tried to pick and choose the best and most easily grasped ideas and analyses from the list.