File: Gather.pm

package info (click to toggle)
libsyntax-keyword-gather-perl 1.003002-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster
  • size: 120 kB
  • sloc: perl: 135; makefile: 2
file content (288 lines) | stat: -rw-r--r-- 6,997 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
package Syntax::Keyword::Gather;
$Syntax::Keyword::Gather::VERSION = '1.003002';
use strict;
use warnings;

# ABSTRACT: Implements the Perl 6 'gather/take' control structure in Perl 5

use Carp 'croak';

use Sub::Exporter::Progressive -setup => {
   exports => [qw{ break gather gathered take }],
   groups => {
      default => [qw{ break gather gathered take }],
   },
};

my %gatherers;

sub gather(&) {
   croak "Useless use of 'gather' in void context" unless defined wantarray;
   my ($code) = @_;
   my $caller = caller;
   local @_;
   push @{$gatherers{$caller}}, bless \@_, 'Syntax::Keyword::Gather::MagicArrayRef';
   die $@
      if !eval{ &$code } && $@ && !UNIVERSAL::isa($@, 'Syntax::Keyword::Gather::Break');
   return @{pop @{$gatherers{$caller}}} if wantarray;
   return   pop @{$gatherers{$caller}}  if defined wantarray;
}

sub gathered() {
   my $caller = caller;
   croak "Call to gathered not inside a gather" unless @{$gatherers{$caller}};
   return $gatherers{$caller}[-1];
}

sub take(@) {
   my $caller = caller;
   croak "Call to take not inside a gather block"
      unless ((caller 3)[3]||"") eq 'Syntax::Keyword::Gather::gather';
   @_ = $_ unless @_;
   push @{$gatherers{$caller}[-1]}, @_;
   return 0+@_;
}

my $breaker = bless [], 'Syntax::Keyword::Gather::Break';

sub break() {
   die $breaker;
}

package Syntax::Keyword::Gather::MagicArrayRef;
$Syntax::Keyword::Gather::MagicArrayRef::VERSION = '1.003002';
use overload
   'bool'   => sub { @{$_[0]} > 0      },
   '0+'     => sub { @{$_[0]} + 0      },
   '""'     => sub { join q{}, @{$_[0]} },
   fallback => 1;

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Syntax::Keyword::Gather - Implements the Perl 6 'gather/take' control structure in Perl 5

=head1 VERSION

version 1.003002

=head1 SYNOPSIS

 use Syntax::Keyword::Gather;

 my @list = gather {
    # Try to extract odd numbers and odd number names...
    for (@data) {
       if (/(one|three|five|seven|nine)$/) { take qq{'$_'} }
       elsif (/^\d+$/ && $_ %2)            { take $_ }
    }
    # But use the default set if there aren't any of either...
    take @defaults unless gathered;
 }

or to use the stuff that L<Sub::Exporter> gives us, try

 # this is a silly idea
 use syntax gather => {
   gather => { -as => 'bake' },
   take   => { -as => 'cake' },
 };

 my @vals = bake { cake (1...10) };

=head1 DESCRIPTION

Perl 6 provides a new control structure -- C<gather> -- that allows
lists to be constructed procedurally, without the need for a temporary
variable. Within the block/closure controlled by a C<gather> any call to
C<take> pushes that call's argument list to an implicitly created array.
C<take> returns the number of elements it took.  This module implements
that control structure.

At the end of the block's execution, the C<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:

 print do {
    my @wanted;
    while (my $line = <>) {
       push @wanted, $line  if $line =~ /\D/;
       push @wanted, -$line if some_other_condition($line);
    }
    push @wanted, 'EOF';
    join q{, }, @wanted;
 };

instead we can write:

 print join q{, }, gather {
    while (my $line = <>) {
       take $line  if $line =~ /\D/;
       take -$line if some_other_condition($line);
    }
    take 'EOF';
 }

and instead of:

 my $text = do {
    my $string;
    while (<>) {
       next if /^#|^\s*$/;
       last if /^__[DATA|END]__\n$/;
       $string .= $_;
    }
    $string;
 };

we could write:

 my $text = join q{}, gather {
    while (<>) {
       next if /^#|^\s*$/;
       last if /^__[DATA|END]__\n$/;
       take $_;
    }
 };

There is also a third function -- C<gathered> -- which returns a
reference to the implicit array being gathered. This is useful for
handling defaults:

 my @odds = gather {
    for @data {
       take $_ if $_ % 2;
       take to_num($_) if /[one|three|five|nine]$/;
    }
    take (1,3,5,7,9) unless gathered;
 }

Note that -- as the example above implies -- the C<gathered> function
returns a special Perl 5 array reference that acts like a Perl 6 array
reference in boolean, numeric, and string contexts.

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:

 my @odds = gather {
    for @data {
       take $_ if $_ %2;
       take to_num($_) if /[one|three|five|seven|nine]$/;
    }
    unshift gathered, +grep(/[a-z]/i, @data);
 }

Conceptually C<gather>/C<take> is the generalized form from which both
C<map> and C<grep> derive. That is, we could implement those two functions
as:

 sub map (&@) {
   my $coderef = shift;
   my @list = @{shift @_};

   return gather {
      take $coderef->($_) for (@list)
   };
 }

 sub grep (&@) {
   my $coderef = shift;
   my @list = @{shift @_};

   return gather {
      do { take $_ if $coderef->($_) } for @list
   };
 }

A C<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>;
    while (1) {
       if ( defined $a && defined $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 }
    }
 }

If you like it really short, you can also C<gather>/C<take> $_ magically:

my @numbers_with_two = gather {
    for (1..20) {
        take if /2/
    }
};
# @numbers_with_two contains 2, 12, 20

Be aware that $_ in Perl5 is a global variable rather than the
current topic like in Perl6.

=head1 HISTORY

This module was forked from Damian Conway's L<Perl6::Gather> for a few reasons.

=over 1

=item to avoid the slightly incendiary name

=item to avoid the use of the Perl6::Exporter

=item ~ doesn't overload to mean string context

=back

=head1 BUGS AND IRRITATIONS

It would be nice to be able to code the default case as:

 my @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 C<or> imposes a scalar context on its left argument.
This is arguably a bug and definitely an irritation.

=head1 AUTHORS

=over 4

=item *

Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com>

=item *

Damian Conway

=back

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2018 by Arthur Axel "fREW" Schmidt.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut