Exegesis 4
by Damian Conway
|
Pages: 1, 2, 3, 4, 5, 6
Editor's note: this document is out of date and remains here for historic interest. See Synopsis 4 for the current design information.
Lexical Exceptions
Most of the implementation of the calculator is contained in the
Calc module. In Perl 6, modules are specified using the module keyword:
module Calc;
which is similar in effect to a Perl 5:
# Perl 5 code
package Calc;
Modules are not quite the same as packages in Perl 6. Most significantly, they have a different export mechanism: They export via a new, built-in, declarative mechanism (which will be described in a future Apocalypse) and the symbols they export are exported lexically by default.
The first thing to appear in the module is a class declaration:
my class NoData is Exception {
method warn(*@args) { die @args }
}
This is another class derived from Exception, but one that has two significant differences from the declaration of class Err::BadData:
-
The leading
mymakes it lexical in scope, and - the trailing braces give it an associated block in which its attributes and methods can be specified.
Let's look at each of those.
NoData exceptions are only going to be used within the Calc module itself. So it's good software engineering to make them visible only within the module itself.
Why? Because if we ever attempt to refer to the exception class outside Calc (e.g. if we tried to catch such an exception in main), then we'll get a compile-time "No such class: NoData" error. Any such errors would indicate a flaw in our class design or implementation.
In Perl 6, classes are first-class constructs. That is, like variables and subroutines, they are "tangible" components of a program, denizens of a symbol table, able to be referred to both symbolically and by explicit reference:
$class = \Some::Previously::Defined::Class;
# and later
$obj = $class.new();
Note that the back slash is actually optional in that first line, just as it would be for an array or hash in the same position.
"First class" also means that classnames live in a symbol table. So it follows that they can be defined to live in the current lexical symbol table (i.e. %MY::), by placing a my before them.
A lexical class or module is only accessible in the lexical scope in which it's declared. Of course, like Perl 5 packages, Perl 6 classes and modules don't usually have an explicit lexical scope associated with their declaration. They are implicitly associated with the surrounding lexical scope (which is normally a file scope).
But we can give them their own lexical scope to preside over by adding a block at the end of their declaration:
class Whatever {
# definition here
}
This turns out to be important. Without the ability to specify a lexical scope over which the class has effect, we would be stuck with no way to embed a "nested" lexical class:
class Outer;
# class Outer's namespace
my class Inner;
# From this line to the end of the file
# is now in class Inner's namespace
In Perl 6, we avoid this problem by writing:
class Outer;
# class Outer's namespace
my class Inner {
# class Inner's namespace
}
# class Outer's namespace again
In our example, we use this new feature to redefine NoData's warn
method (upgrading it to a call to die). Of course, we could also have done that with just:
my class NoData is Exception; # Open NoData's namespace
method warn(*@args) { die @args } # Defined in NoData's namespace
but then we would have needed to "reopen" the Calc module's namespace afterward:
module Calc; # Open Calc's namespace
my class NoData is Exception; # Open NoData's (nested) namespace
method warn(*@args) { die @args } # Defined in NoData's namespace
module Calc; # Back to Calc's namespace
Being able to "nest" the NoData namespace:
module Calc; # Open Calc's namespace
my class NoData is Exception { # Open NoData's (nested) namespace
method warn(*@args) { die @args } # Defined in NoData's namespace
}
# The rest of module Calc defined here.
is much cleaner.
By the way, because classes can now have an associated block, they can even be anonymous:
$anon_class = class {
# definition here
};
# and later
$obj = $anon_class.new();
which is a handy way of implementing "singleton" objects:
my $allocator = class {
my $.count = "ID_000001";
method next_ID { $.count++ }
}.new;
# and later...
for @objects {
$_.set_id( $allocator.next_ID );
}
Maintaining Your State
To store the values of any variables used by the calculator, we'll use a single hash, with each key being a variable name:
my %var;
Nothing more to see here. Let's move along.
It's a Given
The get_data subroutine may be given a number (i.e. a literal value),
a numerical variable name (i.e. '$1', '$2', etc.) , or the keyword 'previous'.
It then looks up the information in the %var hash, using a switch statement
to determine the appropriate look-up:
my sub get_data ($data) {
given $data {
The given $data evaluates its first argument (in this case, $data) in a scalar context, and makes the result the "topic" of each subsequent
when inside the block associated with the given. (Though, just between us, that block is merely an anonymous closure acting as the given's second argument -- in Perl 6 all blocks are merely closures that are slumming it.)
Note that the given $data statement also makes $_ an alias for $data. So, for example, if the when specifies a pattern:
when /^\d+$/ { return %var{""} = $_ }
then that pattern is matched against the contents of $data (i.e. against the current topic). Likewise, caching and returning $_ when the pattern matches is the same as caching and returning $data.
After a when's block has been selected and executed, control automatically passes to the end of the surrounding given (or, more generally, to the end of whatever block provided the when's topic). That means that when blocks don't "fall through" in the way that case statements do in C.
You can also explicitly send control to the end of a when's surrounding given, using a break statement. For example:
given $number {
when /[02468]$/ {
if ($_ == 2) {
warn "$_ is even and prime\n";
break;
}
warn "$_ is even and composite\n";
}
when &is_prime {
warn "$_ is odd and prime\n";
}
warn "$_ is odd and composite\n";
}
Alternatively, you can explicitly tell Perl not to automatically break at the end of the when block. That is, tell it to "fall through" to the statement immediately after the when. That's done with a continue statement (which is the new name for The Statement Formerly Known As skip):
given $number {
when &is_prime { warn "$_ is prime\n"; continue; }
when /[13579]$/ { warn "$_ is odd"; }
when /[02468]$/ { warn "$_ is even"; }
}
In Perl 6, a continue means: "continue executing from the next statement after the current when, rather than jumping out of the surrounding given." It has nothing to do with the old Perl 5 continue block, which in Perl 6 becomes NEXT.
The "topic" that given creates can also be aliased to a name of our own choosing (though it's always aliased to $_ no matter what else we may do). To give the topic a more meaningful name, we just need to use the "topical arrow:"
given check_online().{active}{names}[0] -> $name {
when /^\w+$/ { print "$name's on first\n" }
when /\?\?\?/ { print "Who's on first\n" }
}
Having been replaced by the dot, the old Perl 5 arrow operator is given a new
role in Perl 6. When placed after the topic specifier of a control structure
(i.e. the scalar argument of a given, or the list of a for), it allows us to give an extra name (apart from $_) to the topic associated with that control structure.
In the above version, the given statement declares a lexical variable $name and makes it yet another way of referring to the current topic. That is, it aliases both $name and $_ to the value specified by check_online().{active}{names}[0].
This is a fundamental change from Perl 5, where $_ was only aliased to the current topic in a for loop. In Perl 6, the current topic -- whatever
its name and however you make it the topic -- is always aliased to $_.
That implies that everywhere that Perl 5 used $_ as a default (i.e. print, chomp, split, length, eval, etc.), Perl 6 uses the current topic:
for @list -> $next { # iterate @list, aliasing each element to
# $next (and to $_)
print if length > 10; # same as: print $next if length $next > 10
%count{$next}++;
}
This is subtly different from the "equivalent" Perl 5 code:
# Perl 5 code
for my $next (@list) { # iterate @list, aliasing each element to
# $next (but not to $_)
print if length > 10; # same as: print $_ if length $_ > 10
# using the $_ value from *outside* the loop
%count{$next}++;
}
If you had wanted this Perl 5 behavior in Perl 6, then you'd have to say explicitly what you meant:
my $outer_underscore := $_;
for @list -> $next {
print $outer_underscore
if length $outer_underscore > 10;
%count{$next}++;
}
which is probably a good thing in code that subtle.
Oh, and yes: the p52p6 translator program will take that new
behavior into account and correctly convert something pathological like:
# Perl 5 code
while (<>) {
for my $elem (@list) {
print if $elem % 2;
}
}
to:
# Perl 6 code
for <> {
my $some_magic_temporary_variable := $_;
for @list -> $elem {
print $some_magic_temporary_variable if $elem % 2;
}
}
Note that this works because, in Perl 6, a call to <> is lazily evaluated in list contexts, including the list of a for loop.

