Sign In/My Account | View Cart  
advertisement


Listen Print

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.

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11

Next Pagearrow