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.
Would you like an adverb with that?
OK, so now our ∑ operator can take a modifying filter. How exactly do we pass that filter to it?As described earlier, the colon is used to introduce adverbial arguments into the argument list of a subroutine or operator. So to do a normal summation we write:
$sum = ∑ @costs;whilst to do a filtered summation we place the filter after a colon at the end of the regular argument list:
$sum = ∑ @costs : sub {$_ >= 1000};
or, more elegantly, using a higher-order function:
$sum = ∑ @costs : {$^_ >= 1000};
Any arguments after the colon are bound to the parameters specified
by the subroutine's adverbial parameter list.
Note that the example also demonstrates that we can interpolate the results of the various summations directly into output strings. We do this using Perl 6's scalar interpolation mechanism ($(...)), like so:
print "Total expenditure: $( ∑ @costs )\n";
print "Major expenditure: $( ∑ @costs : {$^_ >= 1000} )\n";
print "Minor expenditure: $( ∑ @costs : {$^_ < 1000} )\n";
The odd lazy step
|
|
Finally (and only because we can), we print out a list of every second element of @costs. There are numerous ways to do that in Perl 6, but the cutest is to use a lazy, infinite, stepped list of indices in a regular slicing operation.
In Perl 6, any list of values created with the .. operator is created lazily. That is, the .. operator doesn't actually build a list of all the values in the specified range, it creates an array object that knows the boundaries of the range and can interpolate (and then cache) any given value when it's actually needed. That's useful, because it greatly speeds up the creation of a list like (1..Inf).
Inf is Perl 6's standard numerical infinity value, so a list that runs to Inf takes ... well ... forever to actually build. But writing 1..Inf is OK in Perl 6, since the elements of the resulting list are only ever computed on demand. Of course, if you were to print(1..Inf), you'd have plenty of time to go and get a cup of coffee. And even then (given the comparatively imminent heat death of the universe) that coffee would be really cold before the output was complete. So there will probably be a warning when you try to do that.
But to get an infinite list of odd indices, we don't want every number between 1 and infinity; we want every second number. Fortunately, Perl 6's .. operator can take an adverb that specifies a "step-size" between the elements in the resulting list. So if we write (1..Inf : 2), we get (1,3,5,7,...). Using that list, we can extract the oddly indexed elements of an array of any size (e.g. @costs) with an ordinary array slice:
print @costs[1..Inf:2]You might have expected another one of those "maximal-entropy coffee" delays whilst print patiently outputs the infinite number of undef's that theoretically exist after @costs' last element, but slices involving infinite lists avoid that problem by returning only those elements that actually exist in the list being sliced. That is, instead of iterating the requested indices in a manner analogous to:
sub slice is lvalue (@array, *@wanted_indices) {
my @slice;
foreach $wanted_index ( @wanted_indices ) {
@slice[+@slice] := @array[$wanted_index];
}
return @slice;
}
infinite slices iterate the available indices:
sub slice is lvalue (@array, *@wanted_indices) {
my @slice;
foreach $actual_index ( 0..@array.last ) {
@slice[+@slice] := @array[$actual_index]
if any(@wanted_indices) == $actual_index;
}
return @slice;
}
(Obviously, it's actually far more complicated -- and lazy -- than that.
It has to preserve the original ordering of the wanted indexes, as well
as cope with complex cases like infinite slices of infinite lists. But
from the programmer's point of view, it all just DWYMs).
By the way, binding selected array elements to the elements of another array (as in: @slice[+@slice] := @array[$actual_index]), and then returning the bound array as an lvalue, is a neat Perl 6 idiom for recreating any kind of slice-like semantics with user-defined subroutines.
Take that! And that!
And so, lastly, we save the data back to disk: save_data(%data, log => {name=>'metalog', vers=>1, costs=>[], stat=>0});
Note that we're passing in both a hash and a pair, but that these still
get correctly folded into &save_data's single hash parameter,
courtesy of the flattening asterisk on the parameter definition:
sub save_data (*%data) {...
In a nutshell...
It's okay if your head is spinning at this point.We just crammed a huge number of syntactic and semantic changes into a comparatively small piece of example code. The changes may seem overwhelming, but that's because we've been concentrating on only the changes. Most of the syntax and semantics of Perl's operators don't change at all in Perl 6.
So, to conclude, here's a summary of what's new, what's different, and (most of all) what stays the same.
Unchanged operators
- prefix and postfix ++ and --
- unary !, ~, \, and -
- binary **
- binary =~ and !~
- binary *, /, and %
- binary + and -
- binary << and >>
- binary & and |
- binary =, +=, -=, *=, etc.
- binary ,
- unary not
- binary and, or, and xor
Changes to existing operators
- binary -> (dereference) becomes .
- binary . (concatenate) becomes _
- unary + (identity) now enforces numeric context on its argument
- binary ^ (bitwise xor) becomes ~
- binary => becomes the "pair" constructor
- ternary ? : bbeeccoommeess ?? ::
Enhancements to existing operators
- binary .. becomes even lazier
- binary <, >, lt, gt, ==, !=, etc. become chainable
- Unary -r, -w, -x, etc. are nestable
- The <> input operator are more context-aware
- The logical && and || operators propagate their context to both their operands
- The x repetition operator no longer requires listifying parentheses on its left argument in a list context.
New operators:
- unary _ is the explicit string context enforcer
- binary ~~ is high-precedence logical xor
- unary * is a list context specifier for parameters and a array flattening operator for arguments
- unary ^ is a meta-operator for specifying vector operations
- unary := is used to create aliased variables (a.k.a. binding)
- unary // is the logical 'default' operator


