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.
Variadic transitions
There are, then, two basic transitions in argument processing. First
is the transition from positional to named arguments. The second is
from named arguments to the variadic list. It's also possible to
transition directly from positional arguments to the variadic list
if optional positional arguments have been completely specified.
That is, the slurp array could just be considered the next optional
positional parameter in that case, as it is in push.
But what if you don't want to fill out all the optional parameters, and
you aren't planning to use named notation to skip the rest of them?
How can you make both transitions simultaneously? There are two
workarounds. First, suppose we have a push-like signature such
as this:
sub stuff (@array, ?$how, *@list) {...}
The declarative workaround is to move the optional parameters after the slurp array, so that they are required to be specified as named parameters:
sub stuff (@array, *@list, +$how) {...}
Then you can treat the slurp array as a positional parameter.
That's the solution we used to add an extra argument to push
earlier, where the list always starts at the second argument.
On the calling end, you don't have any control of the declaration, but you can always specify one of the arguments as named, either the final positional one, or the list itself:
stuff(@foo, how => undef, 1,2,3)
stuff(@foo, list => (1,2,3))
The latter is clearer and arguably more correct, but it has a couple of minor problems. For one thing, you have to know what the parameter name is. It's all very well if you have to know the names of optional parameters, but every list operator has a list that you really ought to be able to feed without knowing its name.
So we'll just say that the actual name of the slurpy list parameter is
"*@". You can always say this:
stuff(@foo, '*@' => (1,2,3))
That's still a lot of extra unnecessary cruft--but we can do better.
List operators are like commands in Unix, where there's a command line
containing a program name and some options, and streams of data coming
in and going out via pipes. The command in this case is stuff,
and the option is @foo, which says what it is we're stuffing.
But what about the streams of stuff going in and out? Perl 6 has
lazy lists, so they are in fact more like streams than they used to be.
There will be two new operators, called pipe operators, that allow us to hook list generators together with list consumers in either order. So either of these works:
stuff @foo <== 1,2,3
1,2,3 ==> stuff @foo
The (ir)rationale for this is provided in Appendix A.
To be sure, these newfangled pipe operators do still pass the
list as a "*@"-named argument, because that allows indirection in the
entire argument list. Instead of:
1,2,3 ==> stuff @foo
you can pull everything out in front, including the positional and
named parameters, and build a list that gets passed as "splat"
arguments (described in the next section) to stuff:
list(@foo, how => 'scrambled' <== 1,2,3)
==> stuff *;
In other words:
list(@foo, how => 'scrambled' <== 1,2,3) ==> stuff *;
is equivalent to:
list(@foo, how => 'scrambled' <== 1,2,3) ==> stuff *();
which is equivalent to:
stuff *(list(@foo, how => 'scrambled' <== 1,2,3));
The "splat" and the list counteract each other, producing:
stuff(@foo, how => 'scrambled' <== 1,2,3);
So what stuff actually sees is exactly as if you called it like this:
stuff(@foo, how => 'scrambled', '*@' => (1,2,3));
which is equivalent to:
stuff @foo, how => 'scrambled', 1, 2, 3;
And yes, the ==> and <== operators are big, fat, and
obnoxiously noticeable. I like them that way. I think the pipes
are important and should stand out. In postmodern architecture
the ducts are just part of the deconstructed decor. (Just don't
anyone suggest a ==>= operator. Just...don't.)
The ==> and <== operators have the additional side
effect of forcing their blunt end into list context and their pointy
end into scalar context. (More precisely, it's not the expression
on the pointy end that is in scalar context, but rather the positional
arguments of whatever list function is pointed to by the pointy end.)
See Appendix A for details.
Context
As with Perl 5, the scalar arguments are evaluated in scalar context, while the list arguments are evaluated in list context. However, there are a few wrinkles.
Overriding signature with *
Perl 5 has a syntax for calling a function without paying any attention
to its prototype, but in Perl 6 that syntax has been stolen for a
higher purpose (referential purity). Also, sometimes you'd like to be
able to ignore part of a signature rather than the whole signature.
So Perl 6 has a different notation, unary *, for disabling
signature checking, which we've mentioned in earlier Apocalypses,
and which you've already seen in the form of the stuff * above.
(Our splat in the stuff * above is in fact unary, but the optional
argument is missing, because the list is supplied via pipe.)
The first splatted term in an argument list causes all prior terms to be evaluated in scalar context, and all subsequent terms to be evaluated in list context. (Splat is a no-op in list context, so it doesn't matter if there are more splatted terms.) If the function wants more positional arguments, they are assumed to come from the generated list, as if the list had been specified literally in the program at that point as comma-separated values.
With splat lists, some of the argument processing may have to be deferred from compile time to runtime, so in general such a call may run slower than the ordinary form.
Context unknown at compile time
If Perl can't figure out the signature of a function at compile time (because, for instance, it's a method and not a function), then it may not be known which arguments are in scalar or list context at the time they are evaluated. This doesn't matter for Perl variables, because in Perl 6, they always return a reference in either scalar or list context. But if you call a function in such an indeterminate context, and the function doesn't have a return value declared that clarifies whether the function behaves differently in scalar or list context, then one of two things must happen. The function must either run in an indeterminate context, or the actual call to the function must be delayed until the context is known. It is not yet clear which of these approaches is the lesser evil. It may well depend on whether the function pays more attention to its dynamic context or to global values. A function with no side effects and no global or dynamic dependencies can be called whenever we like, but we're not here to enforce the functional paradigm. Interesting functions may pay attention to their context, and they may have side effects such as reading from an input stream in a particular order.
A variant of running in indeterminate context is to simply assume the
function is running in list context. (That is, after all, what Perl
5 does on methods and on not-yet-declared subroutines.) In Perl 6,
we may see most such ambiguities resolved by explicit use of the <==
operator to force preceding args into scalar context, and the
following args into list context. Individual arguments may also be
forced into scalar or list context, of course.
By the way, if you mix unary splat with <==, only the args
to the left of the splat are forced into scalar context. (It can do
this because <== governs everything back to the list operator,
since it has a precedence slightly looser than comma.) So, given
something like:
@moreargs = (1,2,3);
mumble $a, @b, c(), *@moreargs <== @list;
we can tell just by looking that $a, @b, and c() are all
evaluated in scalar context, while @moreargs and @list are both
in list context. It is parsed like this:
mumble( ($a, @b, c(), (*@moreargs)) <== (@list) );
You might also write that like this:
@moreargs = list(1,2,3 <== @list);
mumble $a, @b, c(), *@moreargs;
In this case, we can still assume that $a, @b, c() are in
scalar context, because as we mentioned in the previous section,
the * forces it. (That's because there's no reason to put the
splat if you're already in list context.)
Before we continue, you probably need a break. Here, have a break:
*******************************************************
******************** Intermission *********************
*******************************************************
Variations on a theme
Welcome back.
We've covered the basics up till now, but there are a number of miscellaneous variations we left out in the interests of exposition. We'll now go back to visit some of those issues.
Typed slurps
Sometimes you want to specify that the variadic list has a particular recurring type, or types. This falls out naturally from the slurp array syntax:
sub list_of_ints ($a, $b, Int *@ints) { ... }
sub list_of_scalars (Scalar *@scalars) { ... }
These still evaluate the list in list context. But if you declare them as:
sub intlist ($a, $b, Int *@ints is context(Int)) { ... }
sub scalarlist (Scalar *@scalars is context(Scalar)) { ... }
then these provide a list of Int or Scalar contexts to the
caller. If you call:
scalarlist(@foo, %bar, baz())
you get two scalar references and the scalar result of baz(), not a
flattened list. You can have lists without list context in Perl 6!
If you want to have alternating types in your list, you can. Just specify a tuple type on your context:
strintlist( *@strints is context(Str,Int)) { ... }
Perl 5's list context did not do lazy evaluation, but always flattened
immediately. In Perl 6 the default list context "is context(Lazy)".
But you can specify "is context(Eager)" to get back to Perl 5
semantics of immediate flattening.
As a sop to the Perl5-to-Perl6 translator (and to people who have to
read translated programs), the Eager context can also be specified
by doubling the slurpy * on the list to make it look like a pair of
rollers that will squish anything flat:
sub p5func ($arg, **@list) { ... }
The "eager splat" is also available as a unary operator to attempt eager flattening on the rvalue side:
@foo = **1..Inf; # Test our "out of memory" handler...
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 |

