Sign In/My Account | View Cart  
advertisement


Listen Print

Exegesis 6

by Damian Conway
July 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;
}

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11

Next Pagearrow