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.
It's a setup
In Perl 5, it's not uncommon to see people using the ||= operator to set up default values for subroutine parameters or input data:
$offset ||= 1;
$suffix ||= $last_suffix || $default_suffix || '.txt';
# etc.
|
Related Articles |
Of course, unless you're sure of your range of values, this can go horribly wrong -- specifically, if the variable being initialized already has a valid value that Perl happens to consider false (i.e if $suffix or $last_suffix or $default_suffix contained an empty string, or the offset really was meant to be zero).
So people have been forced to write default initializers like this:
$offset = 1 unless defined $offset;
which is OK for a single alternative, but quickly becomes unwieldy when there are several alternatives:
$suffix = $last_suffix unless defined $suffix;
$suffix = $default_suffix unless defined $suffix;
$suffix = '.txt' unless defined $suffix;
Perl 6 introduces a binary 'default' operator -- // -- that solves this problem. The default operator evaluates to its left operand if that operand is defined, otherwise it evaluates to its right operand. When chained together, a sequence of // operators evaluates to the first operand in the sequence that is defined. And, of course, the assignment variant -- //= -- only assigns to its lvalue if that lvalue is currently undefined.
The symbol for the operator was chosen to be reminiscent of a ||, but one that's taking a slightly different angle on things.
So &load_data ensures that its parameters have sensible defaults like this:
$version //= 1;
@dirpath //= @last_dirpath // @std_dirpath // '.';
Note that it will also be possible to provide default values directly in
the specification of optional parameters, probably like this:
sub load_data ($filename ; $version //= 1, *@dirpath //= @std_dirpath) {...}
...and context for all
As if it weren't broken enough already, there's another nasty problem with using || to build default initializers in Perl 5. Namely, that it doesn't work quite as one might expect for arrays or hashes either.If you write:
@last_mailing_list = ('me', 'my@shadow');
# and later...
@mailing_list = @last_mailing_list || @std_mailing_list;then you get a nasty surprise: In Perl 5, || (and &&, for that matter) always evaluates its left argument in scalar context. And in a scalar context an array evaluates to the number of elements it contains, so @last_mailing_list evaluates to 2. And that's what's assigned to @mailing_list instead of the actual two elements.
Perl 6 fixes that problem, too. In Perl 6, both sides of an || (or a && or a //) are evaluated in the same context as the complete expression. That means, in the example above, @last_mailing_list is evaluated in list context, so its two elements are assigned to @mailing_list, as expected.
Substitute our vector, Victor!
The next step in &load_data is to ensure that each path in @dirpath ends in a directory separator. In Perl 5, we might do that with: s{([^/])$}{$1/} foreach @dirpath;
but Perl 6 gives us another alternative: hyper-operators.
Normally, when an array is an operand of a unary or binary operator, it is evaluated in the scalar context imposed by the operator and yields a single result. For example, if we execute:
$account_balance = @credits + @debits;
$biblical_metaphor = @sheep - @goats;
then $account_balance gets the total number of credits plus the
number of debits, and $biblical_metaphor gets the numerical difference
between the number of @sheep and @goats.
That's fine, but this scalar coercion also happens when the operation is in a list context:
@account_balances = @credits + @debits;
@biblical_metaphors = @sheep - @goats;
Many people find it counter-intuitive that these statements each produce
the same scalar result as before and then assign it as the single element
of the respective lvalue arrays.
It would be more reasonable to expect these to act like:
# Perl 5 code...
@account_balances =
map { $credits[$_] + $debits[$_] } 0..max($#credits,$#debits);
@biblical_metaphors =
map { $sheep[$_] - $goats[$_] } 0..max($#sheep,$#goats);
That is, to apply the operation element-by-element, pairwise along the
two arrays.
Perl 6 makes that possible, though not by changing the list context behavior of the existing operators. Instead, Perl 6 provides a "vector" version of each binary operator. Each uses the same symbol as the corresponding scalar operator, but with a caret (^) dangled in front of it. Hence to get the one-to-one addition of corresponding credits and debits, and the list of differences between pairs of sheep and goats, we can write:
@account_balances = @credits ^+ @debits;
@biblical_metaphors = @sheep ^- @goats;
This works for all unary and binary operators, including those that are user-defined. If the two arguments are of different lengths, the operator Does What You Mean (which, depending on the operator, might involve padding with ones, zeroes or undef's, or throwing an exception).
If one of the arguments is a scalar, that operand is replicated as many times as is necessary. For example:
@interest = @account_balances ^* $interest_rate;Which brings us back to the problem of appending those directory separators. The "pattern association" operator (=~) can also be vectorized by prepending a caret, so we can apply the necessary substitution to each element in the @dirpath array like this:
@dirpath ^=~ s{([^/])$}{$1/};
(Pre)fixing those filenames
Having ensured everything is set up correctly, &load_data then processes each candidate file in turn, accumulating data as it goes: my %data;
foreach my $prefix (@dirpath) {
The first step is to create the full file path, by prefixing the current
directory path to the basic filename:
my $filepath = $prefix _ $filename;And here we see the new Perl 6 string concatenation operator: underscore. And yes, we realize it's going to take time to get used to. It may help to think of it as the old dot operator under extreme acceleration.
Underscore is still a valid identifier character, so you need to be careful about spacing it from a preceding or following identifier (just as you've always have with the x or eq operators):
# Perl 6 code # Meaning
$name = getTitle _ getName; # getTitle() . getName()
$name = getTitle_ getName; # getTitle_(getName())
$name = getTitle _getName; # getTitle(_getName())
$name = getTitle_getName; # getTitle_getName()
In Perl 6, there's also a unary form of _. We'll get to that a
little later.

