Hack 90. Glob Those Sequences


Don't settle for counting from one to n by one.

Perl has a syntax for generating simple sequences of increasing integers:

@count_up = 0..100;

There's no syntax for anything more interesting, such as counting by twos or counting downunless you create one yourself.

The Hack

The angle brackets in Perl have two distinct purposes: as a shorthand for calling readline, and as a shorthand for calling glob:

my $input = <$fh>;      # shorthand for: readline($fh) my @files = <*.pl>;     # shorthand for: glob("*.pl")

Assuming you're not interested in that second rather specialized usage (and you can always use the standard File::Glob module, if you are), you can hijack non-readline angles for something much tastier: list comprehensions.

A list comprehension is an expression that filters and transforms one list to create another, more interesting, list. Of course, Perl already has map and grep to do that:

@prime_countdown = grep { is_prime($_) } map { 100-$_ } 0..99;

but doesn't have a dedicated (and optimized) syntax for it:

@prime_countdown = <100..1 : is_prime(X)>;


Running the Hack

By replacing the CORE::GLOBAL::glob( ) subroutine, you replace both the builtin glob( ) function and the angle-bracketed operator version. By rewriting CORE::GLOBAL::glob( ), you can retarget the <...> syntax to do whatever you like, for example, to build sophisticated lists.

Do so with:

package Glob::Lists; use Carp; # Regexes to parse the extended list specifications... my $NUM    = qr{\\s* [+-]? \\d+ (?:\\.\\d*)? \\s* }xms; my $TO     = qr{\\s* \\.\\. \\s*}xms; my $FILTER = qr{ (?: : (.*) )? }xms; my $ABtoZ  = qr{\\A ($NUM) (,) ($NUM) ,? $TO ($NUM) $FILTER \\Z}xms; my $AZxN   = qr{\\A ($NUM) $TO ($NUM) (?:x ($NUM))? $FILTER \\Z}xms; # Install a new glob( ) function... no warnings 'redefine'; *CORE::GLOBAL::glob = sub {     my ($listspec) = @_;     # Does the spec match any of the acceptable forms?     croak "Bad list specification: <$listspec>"         if $listspec !~ $ABtoZ && $listspec !~ $AZxN;     # Extract the range of values and any filter...     my ($from, $to, $incr, $filter) =  $2 eq ',' ? ($1, $4, $3-$1, $5)                                     :              ($1, $2, $3,    $4);     # Work out the implicit increment, if no explicit one...     $incr = $from > $to ? -1 : 1 unless defined $incr;     # Check for nonsensical increments (zero or the wrong sign)...     my $delta = $to - $from;     croak sprintf "Sequence <%s, %s, %s...> will never reach %s",         $from, $from+$incr, $from+2*$incr, $to             if $incr = = 0 || $delta * $incr < 0;     # Generate list of values (and return it, if not filter)...     my @vals = map { $from + $incr * $_ } 0..($delta/$incr);     return @vals unless defined $filter;     # Apply the filter before returning the values...     $filter =~ s/\\b[A-Z]\\b/\\$_/g;     return eval "grep {package ".caller."; $filter } \\@vals"; };

The $ABtoZ and $AZxN regexes match two kinds of sequence specifiers:

<from, then,..to>

and:

<from..to x increment>

and both also allow you to specify a filtering expression after a colon:

<from, then,..to : filter> <from..to x incr : filter>

The regexes capture and extract the relevant start and end values, the increment amount, and the filter. The subroutine then computes the increment in the cases where it is implicit, and checks to see that the sequence makes sense (that is, it isn't something like <1..10 x -1> or <1,2,..-10>).

The code then genereates the sequence using a map, and immediately returns it if there is no filter. If there is a filter, the code evals it into a grep and returns the filtered list instead.

Then you can write:

use Glob::Lists; for ( <1..100 x 7> ) {...}           # 1, 8, 15, 22,...85, 92, 99 my @even_countdown  = <10,8..0>;     # 10, 8, 6, 4, 2, 0 my @fract_countdown = <10,9.5,..0>;  # 10, 9.5, 9,...1, 0.5, 0 my @some_primes = <1..100 x 3 : /7/ && is_prime(N)>;                                      # 7, 37, 67, 73, 79, 97

Hacking the Hack

One of the neater hacks based on this idea is the CPAN module Tie::FTP, which diverts file operations to functions that actually perform those operations on files on remote FTP servers. Another notable module is Tie::STDERR, which provides handy options for diverting STDERR output to email to root, an errorlog file, or an arbitrary function. For Zen-like transcendence of the very concept of "hack" or "purpose", the CPAN module IO::Null can tie to functions that do, and return, nothing at all!



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net