Sign In/My Account | View Cart  
advertisement


Listen Print

Exegesis 3
by Damian Conway | Pages: 1, 2, 3, 4, 5, 6, 7

Editor's note: this document is out of date and remains here for historic interest. See Synopsis 3 for the current design information.

Don't break the chain

Of course, we only want to load the file's data if the file exists, is readable and writable, and isn't too big or too small (say, no less than 100 bytes and no more than a million). In Perl 5 that would be:
    if (-e $filepath  &&  -r $filepath  &&  -w $filepath  and
        100 < -s $filepath  &&  -s $filepath <= 1e6) {...
which has far too many &&'s and $filepath's for its own good.

In Perl 6, the same set of tests can be considerably abbreviated by taking advantage of two new types of operator chaining:

    if (-w -r -e $filepath  and  100 < -s $filepath <= 1e6) {...
First, the -X file test operators now all return a special object that evaluates true or false in a boolean context but is really an encapsulated stat buffer, to which subsequent file tests can be applied. So now you can put as many file tests as you like in front of a single filename or filehandle and they must all be true for the whole expression to be true. Note that because these are really nested calls to the various file tests (i.e. -w(-r(-e($filepath)))), the series of tests are effectively evaluated in right-to-left order.

The test of the file size uses another new form of chaining that Perl 6 supports: multiway comparisons. An expression like 100 < -s $filepath <= 1e6 isn't even legal Perl 5, but it Does The Right Thing in Perl 6. More importantly, it short-circuits if the first comparison fails and will evaluate each operand only once.

Open for business

Having verified the file's suitability, we open it for reading and writing:
    my $fh = open $filepath : mode=>'rw' 
        or die "Something screwy with $filepath: $!";
The : mode=>'rw' is an adverbial modifier on the open. We'll see more adverbs shortly.

The $! variable is exactly what you think it is: a container for the last system error message. It's also considerably more than you think it is, since it's also taken over the roles of $? and $@, to become the One True Error Variable.

Applied laziness 101

Contrary to earlier rumors, the "diamond" input operator is alive and well and living in Perl 6 (yes, the Perl Ministry of Truth is even now rewriting Apocalypse 2 to correct the ... err ... "printing error" ... that announced <> would be purged from the language).

So we can happily proceed to read in four lines of data:

    my ($name, $vers, $status, $costs) = <$fh>;
Now, writing something like this is a common Perl 5 mistake -- the list context imposed by the list of lvalues induces <$fh> to read the entire file, create a list of (possibly hundreds of thousands of) lines, assign the first four to the specified variables, and throw the rest away. That's rarely the desired effect.

In Perl 6, this statement works as it should. That is, it works out how many values the lvalue list is actually expecting and then reads only that many lines from the file.

Of course, if we'd written:

    my ($name, $vers, $status, $costs, @and_the_rest) = <$fh>;
then the entire file would have been read.

And now for something completely the same (well, almost)

Apart from the new sigil syntax (i.e. hashes now keep their % signs no matter what they're doing), the remainder of &load_data is exactly as it would have been if we'd written it in Perl 5.

We skip to the next file if the current file's version is wrong. Otherwise, we split the costs line into an array of whitespace-delimited values, and then save everything (including the still-open filehandle) in a nested hash within %data:

            next if $vers < $version;
            $costs = [split /\s+/, $costs];
            %data{$filepath}{qw(fh name vers stat costs)} =
                          ($fh, $name, $vers, $status, $costs);
            }
        }
Then, once we've iterated over all the directories in @dirpath, we return the accumulated data:
        return %data;
    }

The virtue of constancy

Perl 6 variables can be used as constants:
    my @StartOfFile is const = (0,0);
which is a great way to give logical names to literal values, but ensure that those named values aren't accidentally changed in some other part of the code.

Writing it back

When the data is eventually saved, we'll be passing it to the &save_data subroutine in a hash. If we expected the hash to be a real hash variable (or a reference to one), we'd write:
    sub save_data (%data) {...
But since we want to allow for the possibility that the hash is created on the fly (e.g. from a hash-like list of values), we need to use the slurp-it-all-up list context asterisk again:
    sub save_data (*%data) {...

From each according to its ability ...

We then grab each datum for each file with the usual foreach ... values ... construct:
        foreach my $data (values %data) {
and go about saving the data to file.

Your all-in-one input supplier

Because the Perl 6 "diamond" operator can take an arbitrary expression as its argument, it's possible to set a filehandle to read an entire file and do the actual reading, all in a single statement:
    my $rest = <$data.{fh}.irs(undef)>
The variable $data stores a reference to a hash, so to dereference it and access the 'fh' entry, we use the Perl 6 dereferencing operator (dot) and write: $data.{fh}. In practice, we could leave out the operator and just write $data{fh}, since Perl can infer from the $ sigil that we're accessing the hash through a reference held in a scalar. In fact, in Perl 6 the only place you must use an explicit . dereferencer is in a method call. But it never hurts to say exactly what you mean, and there's certainly no difference in performance if you do choose to use the dot.

The .irs(undef) method call then sets the input record separator of the filehandle (i.e. the Perl 6 equivalent of $/) to undef, causing the next read operation to return the remaining contents of the file. And because the filehandle's irs method returns its own invocant -- i.e. the filehandle reference -- the entire expression can be used within the angle brackets of the read.

A variation on this technique allows a Perl program to do a shell-like read-from-filename just as easily:

    my $next_line = <open $filename or die>;
or, indeed, to read the whole file:
    my $all_lines = < open $filename : irs=>undef >;

Pages: 1, 2, 3, 4, 5, 6, 7

Next Pagearrow