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.

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

Next Pagearrow