Exegesis 6
by Damian ConwayJuly 29, 2003
Editor's note: this document is out of date and remains here for historic interest. See Synopsis 6 for the current design information.
As soon as she walked through my door I knew her type: she was an argument waiting to happen. I wondered if the argument was required... or merely optional? Guess I'd know the parameters soon enough.
"I'm Star At Data," she offered.
She made it sound like a pass. But was the pass by name? Or by position?
"I think someone's trying to execute me. Some caller."
"Okay, I'll see what I can find out. Meanwhile, we're gonna have to limit the scope of your accessibility."
"I'd prefer not to be bound like that," she replied.
"I see you know my methods," I shot back.
She just stared at me, like I was a block. Suddenly I wasn't surprised someone wanted to dispatch her.
"I'll return later," she purred. "Meanwhile, I'm counting on you to give me some closure."
It was gonna be another routine investigation.
— Dashiell Hammett, "The Maltese Camel"
This Exegesis explores the new subroutine semantics described in Apocalypse 6. Those new semantics greatly increase the power and flexibility of subroutine definitions, providing required and optional formal parameters, named and positional arguments, a new and extended operator overloading syntax, a far more sophisticated type system, multiple dispatch, compile-time macros, currying, and subroutine wrappers.
As if that weren't bounty enough, Apocalypse 6 also covers the object-oriented subroutines: methods and submethods. We will, however, defer a discussion of those until Exegesis 12.
Playing Our Parts
Suppose we want to be able to partition a list into two arrays (hereafter
known as "sheep" and "goats"), according to some user-supplied criterion. We'll
call the necessary subroutine &part, because it
partitions a list into two parts.
In the most general case, we could specify how &part splits
the list up by passing it a subroutine. &part could then call
that subroutine for each element, placing the element in the "sheep" array if
the subroutine returns true, and into the "goats" array otherwise. It would
then return a list of references to the two resulting arrays.
For example, calling:
($cats, $chattels) = part &is_feline, @animals;
would result in $cats being assigned a reference to an array
containing all the animals that are feline and $chattels being
assigned a reference to an array containing everything else that exists merely
for the convenience of cats.
Note that in the above example (and throughout the remainder of this
discussion), when we're talking about a subroutine as an object in its own
right, we'll use the & sigil; but when we're talking about a
call to the subroutine, there will be no & before its name.
That's a distinction Perl 6 enforces too: subroutine calls never have an
ampersand; references to the corresponding Code object always
do.
Part: The First
The Perl 6 implementation of &part would therefore be:
sub part (Code $is_sheep, *@data) {
my (@sheep, @goats);
for @data {
if $is_sheep($_) { push @sheep, $_ }
else { push @goats, $_ }
}
return (\@sheep, \@goats);
}
As in Perl 5, the sub keyword declares a subroutine. As in Perl
5, the name of the subroutine follows the sub and — assuming
that name doesn't include a package qualifier — the resulting subroutine
is installed into the current package.
Unlike Perl 5, in Perl 6 we are allowed to specify a formal
parameter list after the subroutine's name. This list consists of zero or more
parameter variables. Each of these parameter variables is really a lexical
variable declaration, but because they're in a parameter list we don't need to
(and aren't allowed to!) use the keyword my.
Just as with a regular variable, each parameter can be given a storage type,
indicating what kind of value it is allowed to store. In the above example,
for instance, the $is_sheep parameter is given the type
Code, indicating that it is restricted to objects of that type
(i.e. the first argument must be a subroutine or block).
Each of these parameter variables is automatically scoped to the body of the subroutine, where it can be used to access the arguments with which the subroutine was called.
A word about terminology: an "argument" is a item in the list of data that is passed as part of a subroutine call. A "parameter" is a special variable inside the subroutine itself. So the subroutine call sends arguments, which the subroutine then accesses via its parameters.
Perl 5 has parameters too, but they're not user-specifiable. They're always
called $_[0], $_[1], $_[2], etc.
Not-So-Secret Alias
However, one way in which Perl 5 and Perl 6 parameters are similar is that, unlike Certain Other Languages, Perl parameters don't receive copies of their respective arguments. Instead, Perl parameters become aliases for the corresponding arguments.
That's already the case in Perl 5. So, for example, we can write a temperature conversion utility like:
# Perl 5 code...
sub Fahrenheit_to_Kelvin {
$_[0] -= 32;
$_[0] /= 1.8;
$_[0] += 273.15;
}
# and later...
Fahrenheit_to_Kelvin($reactor_temp);
When the subroutine is called, within the body of
&Fahrenheit_to_Kelvin the $_[0] variable becomes
just another name for $reactor_temp. So the changes the subroutine
makes to $_[0] are really being made to
$reactor_temp, and at the end of the call
$reactor_temp has been converted to the new temperature scale.
That's very handy when we intend to change the values of arguments (as in
the above example), but it's potentially a very nasty trap too. Many
programmers, accustomed to the pass-by-copy semantics of other languages, will
unconsciously fall into the habit of treating the contents of
$_[0] as if they were a copy. Eventually that will lead to some
subroutine unintentionally changing one of its arguments — a bug that is
often very hard to diagnose and frequently even harder to track down.
So Perl 6 modifies the way parameters and arguments interact. Explicit parameters are still aliases to the original arguments, but in Perl 6 they're constant aliases by default. That means, unless we specifically tell Perl 6 otherwise, it's illegal to change an argument by modifying the corresponding parameter within a subroutine.
All of which means that a the naïve translation of
&Fahrenheit_to_Kelvin to Perl 6 isn't going to work:
# Perl 6 code...
sub Fahrenheit_to_Kelvin(Num $temp) {
$temp -= 32;
$temp /= 1.8;
$temp += 273.15;
}
That's because $temp (and hence the actual value it's an alias
for) is treated as a constant within the body of
&Fahrenheit_to_Kelvin. In fact, we'd get a compile time error
message like:
Cannot modify constant parameter ($temp) in &Fahrenheit_to_Kelvin
If we want to be able to modify arguments via Perl 6 parameters, we have to
say so up front, by declaring them is rw ("read-write"):
sub Fahrenheit_to_Kelvin (Num $temp is rw) {
$temp -= 32;
$temp /= 1.8;
$temp += 273.15;
}
This requires a few extra keystrokes when the old behaviour is needed, but
saves a huge amount of hard-to-debug grief in the most common cases. As a
bonus, an explicit is rw declaration means that the compiler can
generally catch mistakes like this:
$absolute_temp = Fahrenheit_to_Kelvin(212);
Because we specified that the $temp argument has to be
read-writeable, the compiler can easily catch attempts to pass in a read-only
value.
Alternatively, we might prefer that $temp not be an alias at
all. We might prefer that &Fahrenheit_to_Kelvin take a
copy of its argument, which we could then modify without affecting the
original, ultimately returning it as our converted value. We can do that too in
Perl 6, using the is copy trait:
sub Fahrenheit_to_Kelvin(Num $temp is copy) {
$temp -= 32;
$temp /= 1.8;
$temp += 273.15;
return $temp;
}

