| Perl6-Gather documentation | Contained in the Perl6-Gather distribution. |
Perl6::Gather - Implements the Perl 6 'gather/take' control structure in Perl 5
use Perl6::Gather;
@list = gather {
# Try to extract odd numbers and odd number names...
for (@data) {
if (/(one|three|five|nine)$/) { take qq{'$_'}; }
elsif (/^\d+$/ && $_ %2) { take; }
}
# But use the default set if there aren't any of either...
take @defaults unless gathered;
}
Perl 6 provides a new control structure -- gather -- that allows
lists to be constructed procedurally, without the need for a temporary
variable. Within the block/closure controlled by a gather any call to
take pushes that call's argument list to an implicitly created array.
take returns the number of elements it took.
At the end of the block's execution, the gather returns the list of
values stored in the array (in a list context) or a reference to the array
(in a scalar context).
For example, instead of writing:
# Perl 6 code...
print do {
my @wanted;
for <> -> $line {
push @wanted, $line if $line ~~ /\D/;
push @wanted, -$line if some_other_condition($line);
}
push @wanted, 'EOF';
@wanted;
};
in Perl 6 we can write:
# Perl 6 code...
print gather {
for <> -> $line {
take $line if $line ~~ /\D/;
take -$line if some_other_condition($line);
}
take 'EOF';
}
and instead of:
$text = do {
my $string;
for <> {
next if /^#|^\s*$/;
last if /^__[DATA|END]__\n$/;
$string .= $_;
}
$string;
};
we could write:
$text = ~gather {
for <> {
next if /^#|^\s*$/;
last if /^__[DATA|END]__\n$/;
take;
}
}
As the above example implies, if take is called without any
arguments, it takes the current topic.
There is also a third function -- gathered -- which returns a
reference to the implicit array being gathered. This is useful for
handling defaults:
@odds = gather {
for @data {
take if $_ % 2;
take to_num($_) if /[one|three|five|nine]$/;
}
take 1,3,5,7,9 unless gathered;
}
It's also handy for creating the implicit array by some process more complex than by simple sequential pushing. For example, if we needed to prepend a count of non-numeric items:
@odds = gather {
for @data {
take if $_ %2;
take to_num($_) if /[one|three|five|nine]$/;
}
unshift gathered, +grep(/[a-z]/i, @data);
}
Conceptually gather/take is the generalized form from which both
map and grep derive. That is, we could implement those two functions
as:
sub map ($transform is Code, *@list) {
return gather { for @list { take $transform($_) } };
}
sub grep ($selected is Code|Rule, *@list) {
return gather { for @list { take when $selected } }
}
A gather is also a very handy way of short-circuiting the
construction of a list. For example, suppose we wanted to generate a
single sorted list of lines from two sorted files, but only up to the
first line they have in common. We could gather the lines like this:
my @merged_diff = gather {
my $a = <$fh_a>;
my $b = <$fh_b>;
loop {
if defined all $a,$b {
if $a eq $b { last } # Duplicate means end of list
elsif $a lt $b { take $a; $a = <$fh_a>; }
else { take $b; $b = <$fh_b>; }
}
elsif defined $a { take $a; $a = <$fh_a>; }
elsif defined $b { take $b; $b = <$fh_b>; }
else { last }
}
}
The Perl6::Gather module provides the same functionality in Perl 5. So we could code some of the previous examples like so:
# Perl 5 code...
use Perl6::Gather;
print gather {
for my $line (<>) {
take $line if $line =~ /\D/;
take -$line if some_other_condition($line);
}
take 'EOF';
};
and:
# Perl 5 code...
use Perl6::Gather;
$" = "";
$text = ~gather {
for (<>) {
next if /^#|^\s*$/;
last if /^__(?:DATA|END)__$/;
take;
}
};
and:
# Perl 5 code...
use Perl6::Gather;
@odds = gather {
for (@data) {
take if $_ % 2;
take to_num($_) if /(?:one|three|five|nine)\z/;
}
take 1,3,5,7,9 unless gathered;
};
Note that -- as the second example above implies -- the gathered function
returns a special Perl 5 array reference that acts like a Perl 6 array
reference in boolean, numeric, and string contexts. Note too that that
array reference has the ~ operator overloaded to provide string coercion
(as in Perl 6).
The syntax and semantics of Perl 6 gather's is still being finalized
and consequently is at any time subject to change. The the syntax and
semantics of this module will track those changes when and if they occur.
Damian Conway (damian@conway.org)
Perl6::Export
It would be nice to be able to code the default case as:
@odds = gather {
for (@data) {
take if $_ % 2;
take to_num($_) if /(?:one|three|five|nine)\z/;
}
} or (1,3,5,7,9);
but Perl 5's or imposes a scalar context on its left argument.
This is arguably a bug and definitely an irritation.
Comments, suggestions, and patches welcome.
Copyright (c) 2003, Damian Conway. All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.
| Perl6-Gather documentation | Contained in the Perl6-Gather distribution. |
package Perl6::Gather; use Perl6::Export; use Carp; our $VERSION = '0.42'; my %gatherers; sub gather(&) is export(:DEFAULT) { croak "Useless use of 'gather' in void context" unless defined wantarray; my ($code) = @_; my $caller = caller; local @_; push @{$gatherers{$caller}}, bless \@_, 'Perl6::Gather::MagicArrayRef'; die $@ if !eval{ &$code } && $@ && !UNIVERSAL::isa($@, Perl6::Gather::Break); return @{pop @{$gatherers{$caller}}} if wantarray; return pop @{$gatherers{$caller}} if defined wantarray; } sub gathered() is export(:DEFAULT) { my $caller = caller; croak "Call to gathered not inside a gather" unless @{$gatherers{$caller}}; return $gatherers{$caller}[-1]; } sub take(@) is export(:DEFAULT) { @_ = $_ unless @_; my $caller = caller; croak "Call to take not inside a gather block" unless ((caller 3)[3]||"") eq 'Perl6::Gather::gather'; push @{$gatherers{$caller}[-1]}, @_; return 0+@_; } my $breaker = bless [], 'Perl6::Gather::Break'; sub break() is export(:DEFAULT) { die $breaker; } package Perl6::Gather::MagicArrayRef; use overload 'bool' => sub { @{$_[0]} > 0 }, '0+' => sub { @{$_[0]} + 0 }, '""' => sub { join "", @{$_[0]} }, '~' => sub { join "", @{$_[0]} }, fallback => 1; ; 1; __END__