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 |

