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.
A Constant Source of Joy
Setting up the @def_labels array at compile-time and then using
it as the default for the @labels parameter works fine, but
there's always the chance that the array might somehow be accidentally
reassigned later. If that's not desirable, then we need to make the array a
constant. In Perl 6 that looks like this:
my @def_labels is constant = BEGIN {
print "Enter 2 default labels: ";
split(/\s+/, <>, 3).[0..1];
};
The is constant trait is the way we prevent any Perl 6 variable
from being reassigned after it's been declared. It effectively replaces the
STORE method of the variable's implementation with one that throws
an exception whenever it's called. It also instructs the compiler to keep an
eye out for compile-time-detectable modifications to the variable and die
violently if it finds any.
Whenever a variable is declared is constant it must be
initialized as part of its declaration. In this case we use the return value of
a BEGIN block as the initializer value.
Oh, by the way, BEGIN blocks have return values in Perl 6.
Specifically, they return the value of the last statement executed inside them
(just like a Perl 5 do or eval block does, except
that BEGINs do it at compile-time).
In the above example the result of the BEGIN is the return
value of the call to split. So @def_labels is
initialized to the two default labels, which cannot thereafter be changed.
BEGIN at the Scene of the Crime
Of course, the @def_labels array is really just a temporary
storage facility for transferring the results of the BEGIN block
to the default value of the @labels parameter.
We could easily do away with it entirely, by simply putting the
BEGIN block right there in the parameter list:
sub part (Selector $is_sheep,
Str +@labels is dim(2) = BEGIN {
print "Enter 2 default labels: ";
split(/\s+/, <>, 3).[0..1];
},
*@data
) returns List of Pair
{
# body as before
}
And that works fine.
Macro Biology
The only problem is that it's ugly, brutish, and not at all short. If only
there were some way of calling the BEGIN block at that point
without having to put the actual BEGIN block at that point....
Well, of course there is such a way. In Perl 6 a block is just a special
kind of nameless subroutine... and a subroutine is just a special name-ful kind
of block. So it shouldn't really come as a surprise that BEGIN
blocks have a name-ful, subroutine-ish counterpart. They're called
macros and they look and act very much like ordinary subroutine,
except that they run at compile-time.
So, for example, we could create a compile-time subroutine that requests and returns our user-specified labels:
macro request(int $n, Str $what) returns List of Str {
print "Enter $n $what: ";
my @def_labels = split(/\s+/, <>, $n+1);
return { @def_labels[0..$n-1] };
}
# and later...
sub part (Selector $is_sheep,
Str +@labels is dim(2) = request(2,"default labels"),
*@data
) returns List of Pair
{
# body as before
}
Calls to a macro are invoked during compilation (not at run-time). In fact,
like a BEGIN block, a macro call is executed as soon as the parser
has finished parsing it. So, in the above example, when the parser has parsed
the declaration of the @labels parameter and then the
= sign indicating a default value, it comes across what looks like
a subroutine call. As soon as it has parsed that subroutine call (including its
argument list) it will detect that the subroutine &request is
actually a macro, so it will immediately call &request with
the specified arguments (2 and "default labels").
Whenever a macro like &request is invoked, the parser
itself intercepts the macro's return value and integrates it somehow back into
the parse tree it is in the middle of building. If the macro returns a block
— as &request does in the above example — the
parser extracts the the contents of that block and inserts the parse tree of
those contents into the program's parse tree. In other words, if a macro
returns a block, a precompiled version of whatever is inside the block replaces
the original macro call.
Alternatively, a macro can return a string. In that case, the parser inserts
that string back into the source code in place of the macro call and then
reparses it. This means we could also write &request like
this:
macro request(int $n, Str $what) returns List of Str {
print "Enter $n $what: ";
return "<< ( @(split(/\s+/, <>, $n+1).[0..$n-1]) >>";
}
in which case it would return a string containing the characters
"<<", followed by the two labels that the
request call reads in, followed by a closing double angles. The
parser would then substitute that string in place of the macro call, discover
it was a <<...>> word list, and use that list as the
default labels.
Macros for BEGIN-ners
Macros are enormously powerful. In fact, in Perl 6, we could implement the
functionality of BEGIN itself using a macro:
macro MY_BEGIN (&block) {
my $context = want;
if $context ~~ List {
my @values = block();
return { *@values };
}
elsif $context ~~ Scalar {
my $value = block();
return { $value };
}
else {
block();
return;
}
}
The MY_BEGIN macro declares a single parameter
(&block). Because that parameter is specified with the
Code sigil (&), the macro requires that the
corresponding argument must be a block or subroutine of some type. Within the
body of &MY_BEGIN that argument is bound to the
lexical subroutine &block (just as a
$foo parameter would bind its corresponding argument to a lexical
scalar variable, or a @foo parameter would bind its argument to a
lexical array).
&MY_BEGIN then calls the want function, which
is Perl 6's replacement for wantarray. want returns
a scalar value that simultaneously represents any the contexts in which the
current subroutine was called. In other words, it returns a disjunction of
various classes. We then compare that context information against the three
possibilities — List, Scalar, and (by
elimination) Void.
If MY_BEGIN was called in a list context, we evaluate its
block/closure argument in a list context, capture the results in an array
(@values), and then return a block containing the contents of that
array flattened back to a list. In a scalar context we do much the same thing,
except that MY_BEGIN's argument is evaluated in scalar context and
a block containing that scalar result is returned. In a void context (the only
remaining possibility), the argument is simply evaluated and nothing is
returned.
In the first two cases, returning a block causes the original macro call to
be replaced by a parse tree, specifically, the parse tree representing the
values that resulted from executing the original block passed to
MY_BEGIN.
In the final case — a void context — the compiler isn't expecting to replace the macro call with anything, so it doesn't matter what we return, just as long as we evaluate the block. The macro call itself is simply eliminated from the final parse-tree.
Note that MY_BEGIN could be written more concisely than it was
above, by taking advantage of the smart-matching behaviour of a switch
statement:
macro MY_BEGIN (&block) {
given want {
when List { my @values = block(); return { *@values }; }
when Scalar { my $value = block(); return { $value }; }
when Void { block(); return }
}
}
A Macro by Any Other Syntax ...
Because macros are called by the parser, it's possible to have them interact with the parser itself. In particular, it's possible for a macro to tell the parser how the macro's own argument list should be parsed.
For example, we could give the &request macro its own
non-standard argument syntax, so that instead of calling it as:
request(2,"default labels")
we could just write:
request(2 default labels)
To do that we'd define &request like so:
macro request(int $n, Str $what)
is parsed( /:w \( (\d+) (.*?) \) / )
returns List of Str
{
print "Enter $n $what: ";
my @def_labels = split(/\s+/, <>, $n+1);
return { @def_labels[0..$n-1] };
}
The is parsed trait tells the parser what to look for
immediately after it encounters the macro's name. In the above example, the
parser is told that, after encountering the sequence "request" it
should expect to match the pattern:
/ :w # Allow whitespace between the tokens
\( # Match an opening paren
(\d+) # Capture one-or-more digits
(.*?) # Capture everything else up to...
\) # ...a closing paren
/
Note that the one-or-more-digits and the anything-up-to-paren bits of the
pattern are in capturing parentheses. This is important because the list of
substrings that an is parsed pattern captures is then used as the
argument list to the macro call. The captured digits become the first argument
(which is then bound to the $n parameter) and the captured
"everything else" becomes the second argument (and is bound to
$what).
Normally, of course, we don't need to specify the is parsed
trait when setting up a macro. Since a macro is a kind of subroutine, by
default its argument list is parsed the same as any other subroutine's —
as a comma-separated list of Perl 6 expressions.

