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.

Where return/leave returns to

A return statement needs to return to where the user thinks it ought to return to. Since any block is a closure, any block is really a subroutine in disguise. But the user doesn't generally want return to return from the innermost block, but from the innermost block that was actually defined using an explicit sub-ish keyword. So that's what Perl 6 does. If it can, it will implement the return internally as a simple jump to the end of the subroutine. If it can't, it implements return by throwing a control exception that is caught by the proper context frame.

There will be a leave function that can return from other scopes. By default it exits from the innermost block (anything matching base class Code), but, as with caller and want, you can optionally select the scope you want to return from. It's declared like this:


    multi leave (?$where = Code, *@value, Int +$skip, Str +$label) {...}

which lets you say things like:


    leave;
    leave Block;
    leave &_ <== 1,2,3; # same as "return 1,2,3"
    leave where => Parametric, value => (1,2,3);
    leave Loop, label => 'LINE', $retval
    leave { $_ ~~ Block and $_ !~ Sub } 1,2,3;
    leave () <== 1,2,3;

As it currently stands, the parens aren't optional on that last one, because <== is a binary operator. You could always define yourself a "small" return, ret, that leaves the innermost block:


    my macro ret { "leave Code <== " }
    # and later...
    { ret 1,2,3 }

Note that unlike a return, leave always evaluates any return value in list context. Another thing to iron out is that the context we choose to leave must have set up an exception handler that can handle the control exception that leave must in some cases throw. This seems to imply that any context must miminally catch a control exception that is bound to its own identity, since leave is doing the picking, not the exception handlers.

Subroutine object methods

The .wrap method

You may ask a subroutine to wrap itself up in another subroutine in place, so that calls to the original are intercepted and interpreted by the wrapper, even if access is only through the reference:


    $id = $subref.wrap({
        # preprocessing here
        call;
        # postprocessing here
    }

The call built-in knows how to call the inner function that this function is wrapped around. In a void context, call arranges for the return value of the wrapped routine to be returned implicitly. Alternately, you can fetch the return value yourself from call and return it explicitly:


    $id = $subref.wrap({
        my @retval = call;
        push(@retval, "...and your little dog, too!";
        return @retval;
    })

The arguments arrive in whatever form you request them, independently of how the parameters look to the wrapped routine. If you wish to modify the parameters, supply a new argument list to call:


    $id = $subref.wrap(sub (*@args) {
        call(*@args,1,2,3);
    })

You need to be careful not to preflatten those generators, though.

The $id is useful for removing a particular wrapper:


    $subref.unwrap($id);

We might also at some point allow a built-in sub-like keyword wrap. If we don't, someone will write it anyway.

There is also likely a .wrappers method that represents the list of all the current wrappers of the subroutine. The ordering and manipulation of this list is beyond the scope of this document, but such activity will be necessary for anyone implementing Aspect-Oriented Programming in Perl 6.

The .assuming method

Currying is done with the .assuming method. It works a bit like the .wrap method, except that instead of wrapping in place, it returns a new function to you with a different signature, one in which some of the parameters are assumed to be certain values:


    my &say ::= &*print.assuming(handle => $*TERM);

You can even curry built-in operators:


    my &prefix:½ ::= &infix:/ .assuming(y => 2);

(assuming here that built-in infix operators always use $x and $y).

The .req method

The .req method returns the number of required args requested by the sub in question. It's just a shortcut for digging down into the signature trait and counting up how many required parameters there are. The count includes any invocant (or invocants, for multimethods).

If you want to know how many optional arguments there are, you can do your own digging. This call is primarily for use by madmen who wish to write variants of map and reduce that are sensitive to the number of parameters declared for the supplied block. (Certainly the implementation of for will make heavy use of this information.)

Subroutine traits

These are traits that are declared on the subroutine as a whole, not on any individual parameter.

Internal traits

The signature, returns, and do traits are internal traits containing, respectively, the type signature of the parameters, the type signature of the return value, and the body of the function. Saying:


    sub Num foo (int $one, Str *@many) { return +@many[$one] }

is short for saying something like:


    sub foo is signature( sig(int $one, Str *@many) )
            is returns( sig(Num) )
            will do { return +@many[$one] }

In fact, it's likely that the "do" trait handler has to set up all the linkage to pass parameters in and to trap "return" exceptions.

Many of these pre-defined traits just map straight onto the container object's attribute methods of the same name. Underneath they're just accessors, but we use the trait notation in declarations for several reasons. For one thing, you can string a bunch of them together without repeating the original object, which might be anonymous in any event. It also gives us liberty behind the scenes to promote or demote various traits from mere properties to attributes of every object of a class. It's one of those levels of indirection computer scientists keep talking about...

Going the other direction, it allows us to pretend that accessors are just another form of metadata when accessed as a trait. By the same token it allows us to transparently make our metadata active rather than passive, without rewriting our declarations. This seems useful.

The basic rule of thumb is that you can use any of a container's rw methods as if it were a trait. For subroutine containers, the example above really turns into something like this:


    BEGIN {
        &foo.signature = sig(int $one, Str *@many);
        &foo.returns = sig(Num);
        &foo.do = { return +@many[$one] }
    }

is rw

This trait identifies lvalue subs or methods. See the section on lvalue subs below.

is parsed(<rule>)

This trait binds a macro to a grammar rule for parsing it. The grammar rule is invoked as soon as the initial keyword is seen and before anything else is parsed, so you can completely change the grammar on the fly. For example, the sig() function above might well invoke special parsing rules on its arguments, since what is inside is not an ordinary expression.

In the absence of an explicit <is parsed> trait, a macro's arguments are parsed with whatever macro rule is in effect, by default the standard Perl::macro.

is cloned("BEGIN")

Perhaps this is an alternate way of specifying the parsing and semantics of a macro or function. Or perhaps not. Just an idea for now...

is cached

This is the English translation of what some otherwise sane folks call "memoization". This trait asserts that Perl can do automatic caching of return values based on the assumption that, for any particular set of arguments, the return value is always the same. It can dramatically speed up certain kinds of recursive functions that shouldn't have been written recursively in the first place. ;-)

is inline

This says you think performance would be enhanced if the code were inlined into the calling code. Of course, it also constitutes a promise that you're not intending to redefine it or wrap it or do almost anything else fancy with it, such as expecting it to get called by a method dispatcher. In early versions of Perl 6, it's likely to be completely ignored, I suspect. (If not, it's likely to be completely broken...)

PRE/POST/FIRST/LAST/etc.

These all-caps traits are generally set from the inside of a subroutine as special blocks. FIRST and LAST are expected to have side effects. PRE and POST are expected to not have side effects, but return a boolean value indicating whether pre/post conditions have been met. If you declare any PRE or POST conditions, your routine will automatically be wrapped in a wrapper that evaluates them according to Design-by-Contract principles (ORing preconditions, ANDing postconditions).

Note that the actual "first" or "last" property attached to a subroutine may well be a list of FIRST or LAST blocks, since there can be more than one of them.

Overriding built-ins

All built-in functions that can be overridden are either multimethods or global subroutines. To override one of these, just declare your own subroutine of that name in your current package or lexical scope. For instance, the standard non-filehandle print function may well be declared as:


    multi *print (*@list) {...}

Just declare your own sub:


    sub print (*@list) {...}

to override all print multimethods in the current package, or:


    my sub print (*@list) {...}

to override in the current lexical scope.

To override or wrap a built-in function for everyone (dangerous), you have to play with the globally named version, but we're not going to tell you how to do that. If you can't figure it out, you shouldn't be doing it.

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

Next Pagearrow