Exegesis 6
by Damian Conway
|
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
Editor's note: this document is out of date and remains here for historic interest. See Synopsis 6 for the current design information.
Partitioning Rules!
Now that the when's implicit smart-match is doing the hard work
of deciding how to evaluate each data element against the selector, adding new
kinds of selectors becomes trivial. For example, here's a third version of
&part which also allows Perl 6 rules (i.e. patterns) to be
used to partition a list:
sub part (Code|Class|Rule $is_sheep, *@data) {
my (@sheep, @goats);
for @data {
when $is_sheep { push @sheep, $_ }
default { push @goats, $_ }
}
return (\@sheep, \@goats);
All we needed to do was to tell &part that its first
argument was also allowed to be of type Rule. That allows us to
call &part like this:
($cats, $chattels) = part /meow/, @animal_sounds;
In the scalar context imposed by the $is_sheep parameter, the
/meow/ pattern evaluates to a Rule object (rather
than immediately doing a match). That Rule object is then bound
to $is_sheep and subsequently used as the selector in the
when statement.
Note that the body of this third version is exactly the same as that of the
previous version. No change is required because, when it detects that
$is_sheep is a Rule object, the when's
smart-matching will auto-magically do a pattern match.
In the same way, we could further extend &part to allow the
user to pass a hash as the selector:
my %is_cat = (
cat => 1, tiger => 1, lion => 1, leopard => 1, # etc.
);
($cats, $chattels) = part %is_cat, @animal_names;
simply by changing the parameter list of &part to:
sub part (Code|Class|Rule|Hash $is_sheep, *@data) {
# body exactly as before
}
Once again, the smart-match hidden in the when statement just
Does The Right Thing. On detecting a hash being matched against each datum, it
will use the datum as a key, do a hash look up, and evaluate the truth of the
corresponding entry in the hash.
Of course, the ever-increasing disjunction of allowable selector types is rapidly threatening to overwhelm the entire parameter list. At this point it would make sense to factor the type-junction out, give it a logical name, and use that name instead. To do that, we just write:
type Selector ::= Code | Class | Rule | Hash;
sub part (Selector $is_sheep, *@data) {
# body exactly as before
}
The ::= binding operator is just like the :=
binding operator, except that it operates at compile-time. It's the right
choice here because types need to be fully defined at compile-time, so the
compiler can do as much static type checking as possible.
The effect of the binding is to make the name Selector an alias
for Code | Class |
Rule | Hash. Then we can just use
Selector wherever we want that particular disjunctive type.
Out with the New and in with the Old
Let's take a step back for a moment.
We've already seen how powerful and clean these new-fangled explicit
parameters can be, but maybe you still prefer the Perl 5 approach. After all,
@_ was good enough fer Grandpappy when he lernt hisself Perl as a
boy, dangnabit!
In Perl 6 we can still pass our arguments the old-fashioned way and then process them manually:
# Still valid Perl 6...
sub part {
# Unpack and verify args...
my ($is_sheep, @data) = @_;
croak "First argument to &part is not Code, Hash, Rule, or Class"
unless $is_sheep.isa(Selector);
# Then proceed as before...
my (@sheep, @goats);
for @data {
when $is_sheep { push @sheep, $_ }
default { push @goats, $_ }
}
return (\@sheep, \@goats);
}
If we declare a subroutine without a parameter list, Perl 6 automatically
supplies one for us, consisting of a single slurpy array named
@_:
sub part {...} # means: sub part (*@_) {...}
That is, any un-parametered Perl 6 subroutine expects to flatten and then
slurp up an arbitrarily long list of arguments, binding them to the elements of
a parameter called @_. That's pretty much what a Perl 5 subroutine
does. The only important difference is that in Perl 6 that slurpy
@_ is, like all Perl 6 parameters, constant by default. So, if we
want the exact behaviour of a Perl 5 subroutine — including
being able to modify elements of @_ — we need to be
explicit:
sub part (*@_ is rw) {...}
Note that "declare a subroutine without a parameter list" doesn't mean "declare a subroutine with an empty parameter list":
sub part {...} # without parameter list
sub part () {...} # empty parameter list
An empty parameter list specifies that the subroutine takes exactly zero
arguments, whereas a missing parameter list means it takes any number of
arguments and binds them to the implicit parameter @_.
Of course, by using the implicit @_ instead of named
parameters, we're merely doing extra work that Perl 6 could do for us, as well
as making the subroutine body more complex, harder to maintain, and slower.
We're also eliminating any chance of Perl 6 identifying argument mismatches at
compile-time. And, unless we're prepared to complexify the code even further,
we're preventing client code from using named arguments (see "Name your poison"
below).
But this is Perl, not Fascism. We're not in the business of imposing the One True Coding Style on Perl hackers. So if you want to pass your arguments the old-fashioned way, Perl 6 makes sure you still can.
A Pair of Lists in a List of Pairs
Suppose now that, instead of getting a list of array references back, we
wanted to get back a list of key=>value pairs, where each value
was one of the array refs and each key some kind of identifying label (we'll
see why that might be particularly handy soon).
The easiest solution is to use two fixed keys (for example,
"sheep" and "goats"):
sub part (Selector $is_sheep, *@data) returns List of Pair {
my %herd;
for @data {
when $is_sheep { push %herd{"sheep"}, $_ }
default { push %herd{"goats"}, $_ }
}
return *%herd;
}
The parameter list of the subroutine is unchanged, but now we've added a
return type after it, using the returns keyword. That return type
is List of Pair, which tells the compiler that any
return statements in the subroutine are expected to return a list
of values, each of which is a Perl 6 key=>value pair.
Parametric Types
Note that this type is different from those we've seen so far: it's
compound. The of Pair suffix is actually an argument that modifies
the principal type List, telling the container type what kind of
value it's allowed to store. This is possible because List is a
parametric type. That is, it's a type that can be specified with
arguments that modify how it works. The idea is a little like C++ templates,
except not quite so brain-meltingly complicated.
The specific parameters for a parametric type are normally specified in square brackets, immediately after the class name. The arguments that define a particular instance of the class are likewise passed in square brackets. For example:
class Table[Class $of] {...}
class Logfile[Str $filename] {...}
module SecureOps[AuthKey $key] {...}
# and later:
sub typeset(Table of Contents $toc) {...}
# Expects an object whose class is Table
# and which stores Contents objects
my Logfile["./log"] $file;
# $file can only store logfiles that log to ./log
$plaintext = SecureOps[$KEY]::decode($cryptotext);
# Only use &decode if our $KEY entitles us to
Note that type names like Table of Contents and List of
Pair are really just tidier ways to say
Table[of=>Contents] and List[of=>Pair].
By convention, when we pass an argument to the $of parameter of
a parametric type, we're telling that type what kind of value we're expecting
it to store. For example: whenever we access an element of List of
Pair, we expect to get back a Pair. Similarly we could
specify List of Int, Array of Str, or Hash of
Num.
Admittedly List of Pair doesn't seem much tidier than
List(of=>Pair), but as container types get more complex, the
advantages start to become obvious. For example, consider a data structure
consisting of an array of arrays of arrays of hashes of numbers (such as one
might use to store, say, several years worth of daily climatic data). Using the
of notation that's just:
type Climate::Record ::= Array of Array of Array of Hash of Num;
Without the of keyword, it's:
type Climate::Record ::= Array(of=>Array(of=>Array(of=>Hash(of=>Num))));
which is starting to look uncomfortably like Lisp.
Parametric types may have any number of parameters with any names we like,
but only type parameters named $of have special syntactic support
built into Perl.

