Sign In/My Account | View Cart  
advertisement


Listen Print

Apocalypse 6
by Larry Wall | Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16

Editor's Note: this Apocalypse is out of date and remains here for historic reasons. See Synopsis 06 for the latest information.

The slurpy hash

A hash declaration like *%named indicates that the %named hash should slurp up all the remaining named arguments (that is, those that aren't bound explicitly to a specific formal parameter).

The slurpy array

An array declaration like *@rest indicates that the @rest array should slurp up all the remaining items after the named parameters. (Later we'll discuss how to disambiguate the situation when the beginning of your list looks like named parameters.) If you shift or pop without an argument, it shifts or pops whatever slurpy array is in scope. (So in a sense, your main program has an implicit slurpy array of *@*ARGS because that's what shift shifts there.)

Formal parameter syntax

Formal parameters have lexical scope, as if they were declared with a my. (That is reflected in the pseudocode in Appendix B.) Their scope extends only to the end of the associated block. Formal parameters are the only lexically scoped variables that are allowed to be declared outside their blocks. (Ordinary my and our declarations are always scoped to their surrounding block.)

Any subroutine can have a method signature syntactically, but subsequent semantic analysis will reject mistakes like invocants on subroutines. This is not just motivated by laziness. I think that "You can't have an invocant on a subroutine" is a better error message than "Syntax error".


    rule signature :w {
        [<parameter> [<[,:]> <parameter> ]* ]?
    }

In fact, we just treat colon as a funny comma here, so any use of extra colons is detected in semantic analysis. Similarly, zone markers are semantically restricted, not syntactically. Again, "Syntax error" doesn't tell you much. It's much more informative to see "You can't declare an optional positional parameter like ?$flag after a slurpy parameter like *@list", or "You can't use a zone marker on an invocant".

Here's what an individual parameter looks like:


    rule parameter :w {
        [ <type>? <zone>? <variable> <trait>* <defval>?
        | \[ <signature> \]     # treat single array ref as an arg list
        ]
    }

    rule zone {
        [ \?            # optional positional
        | \*            # slurpy array or hash
        | \+            # optional named-only
        ]
    }

    rule variable { <sigil> <name> [ \( <siglet> \) ]? }
    rule sigil { <[$@%&]> <[*.?^]>? }   # "What is that, swearing?"

Likewise, we parse any sigil here, but semantically reject things like $*x or $?x. We also reject package-qualified names and indirect names. We could have a <simplevar> rule that only admits <ident>, but again, "Syntax error" is a lot less user-friendly than "You can't use a package variable as a parameter, dimwit!"

Similarly, the optional <siglet> in <variable> is allowed only on & parameters, to say what you expect the signature of the referenced subroutine to look like. We should talk about siglets.

Siglets

The <siglet> in the <variable> rule is an example of a nameless signature, that is, a "small signature", or "siglet". Signatures without names are also used for return types and context traits (explained later). A siglet is sequential list of paramlets. The paramlets do not refer to actual variable names, nor do they take defaults:


    rule siglet :w {
        [<paramlet> [<[,:]> <paramlet> ]* ]?
    }

    rule paramlet :w {
        [ <type> <zone>? <varlet>? <trait>*     # require type
        | <zone> <varlet>? <trait>*             # or zone
        | <varlet> <trait>*                     # or varlet
        | \[ <siglet> \]        # treat single array ref as an arg list
        ]
    }

In place of a <variable>, there's a kind of stub we'll call a "varlet":


    rule varlet :w {
        <sigil> [ \( <siglet \) ]?
    }

As with the <variable> rule, a <varlet>'s optional siglet is allowed only on & parameters.

Here's a fancy example with one signature and several siglets.


    sub (int *@) imap ((int *@) &block(int $),
                        int *@vector is context(int) {...}

You're not expected to understand all of that yet. What you should notice, however, is that a paramlet is allowed to be reduced to a type (such as int), or a zone (such as ?), or a varlet (such as $), or some sequence of those (such as int *@). But it's not allowed to be reduced to a null string. A signature of () indicates zero arguments, not one argument that could be anything. Use ($) for that. Nor can you specify four arguments by saying (,,,). You have to put something there.

Perl 6 siglets can boil down to something very much like Perl 5's "prototype pills". However, you can't leave out the comma between parameters in Perl 6. So you have to say ($,$) rather than ($$), when you want to indicate a list of two scalars.

If you use a <siglet> instead of a <signature> in declaring a subroutine, it will be taken as a Perl 5 style prototype, and all args still come in via @_. This is a sop to the Perl5-to-Perl6 translator, which may not be able to figure out how to translate a prototype to a signature if you've done something strange with @_. You should not use this feature in new code. If you use a siglet on a stub declaration, you must use the same siglet on the corresponding definition as well, and vice versa. You can't mix siglets and signatures that way. (This is not a special rule, but a natural consequence of the signature matching rules.)

Siglets and multimethods

For closure parameters like &block(int $), the associated siglet is considered part of its name. This is true not just for parameters, but anywhere you use the & form in your program, because with multimethods there may be several routines sharing the same identifier, distinguishable only by their type signature:


    multi factorial(int $a) { $a<=1 ?? 1 :: $a*factorial($a-1) }
    multi factorial(num $a) { gamma(1+$a) }

    $ref = &factorial;          # illegal--too ambiguous
    $ref = &factorial($);       # illegal--too ambiguous
    $ref = &factorial(int);     # good, means first one.
    $ref = &factorial(num);     # good, means second one.
    $ref = &factorial(complex); # bad, no such multimethod.

Note that when following a name like "&factorial", parentheses do not automatically mean to make a call to the subroutine. (This Apocalypse contradicts earlier Apocalypses. Guess which one is right...)


    $val = &factorial($x);      # illegal, must use either
    $val = factorial($x);       #   this or
    $val = &factorial.($x);     #   maybe this.

In general, don't use the & form when you really want to call something.

Formal parameter traits

Other than type, zone, and variable name, all other information about parameters is specified by the standard trait syntax, generally introduced by is. Internally even the type and zone are just traits, but syntactically they're out in front for psychological reasons. Whose psychological reasons we won't discuss.

is constant (default)

Every formal parameter is constant by default, meaning primarily that the compiler won't feel obligated to construct an lvalue out the actual argument unless you specifically tell it to. It also means that you may not modify the parameter variable in any way. If the parameter is a reference, you may use it to modify the referenced object (if the object lets you), but you can't assign to it and change the original variable passed to the routine.

is rw

The rw trait is how you tell the compiler to ask for an lvalue when evaluating the actual argument for this parameter. Do not confuse this with the rw trait on the subroutine as a whole, which says that the entire subroutine knows how to function as an lvalue. If you set this trait, then you may modify the variable that was passed as the actual argument. A swap routine would be:


    sub swap ($a is rw, $b is rw) { ($a,$b) = ($b,$a) }

If applied to a slurpy parameter, the rw trait distributes to each element of the list that is bound to the parameter. In the case of a slurpy hash, this implies that the named pairs are in an lvalue context, which actually puts the right side of each named pair into lvalue context.

Since normal lvalues assume "is rw", I suppose that also implies that you can assign to a pair:


    (key => $var) = "value";

or even do named parameter binding:


    (who => $name, why => $reason) := (why => $because, who => "me");

which is the same as:


    $name   := "me";
    $reason := $because;

And since a slurpy hash soaks up the rest of the named parameters, this also seems to imply that binding a slurpy rw hash actually makes the hash values into rw aliases:


    $a = "a"; $b = "b";
    *%hash := (a => $a, b => $b);
    %hash{a} = 'x';
    print $a;   # prints "x"

That's kinda scary powerful. I'm not sure I want to document that... ["Too late!" whispers Evil Damian.]

is copy

This trait requests copy-in semantics. The variable is modifiable by you, but you're only modifying your own private copy. It has the same effects as assigning the argument to your own my variable. It does not do copy-out.

If you want both copy-in and copy-out semantics, declare it rw and do your own copying back and forth, preferably with something that works even if you exit by exception (if that's what you want):


    sub cico ($x is rw) {
        my $copy = $x;
        LAST { $x = $copy }
        ...
    }

Though if you're using a copy you probably only want to copy-out on success, so you'd use a KEEP block instead. Or more succinctly, using the new will syntax:


    sub cicomaybe ($x is rw) {
        my $copy will keep { $x = $copy } = $x;
        ...
    }

is ref

This trait explicitly requests call-by-reference semantics. It lets you read and write an existing argument but doesn't attempt to coerce that argument to an lvalue (or autovivify it) on the caller end, as rw would. This trait is distinguished from a parameter of type Ref, which merely asserts that the return type of the parameter is a reference without necessarily saying anything about calling convention. You can without contradiction say:


    sub copyref (Ref $ref is copy) {...}

meaning you can modify $ref, but that doesn't change whatever was passed as the argument for that parameter.

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16

Next Pagearrow