I've run into a problem today with my Perl 6 coding, which is due to
a perceived design flaw in the current Perl 6 spec (this was
discussed on #perl6 just now, mainly between myself and autrijus), so
I'm bringing it up here.
And yes, autrijus thinks the behaviour I'm seeing in Pugs is
according to an actual spec, and mentioned in S02 with a
"Conjectural:" beside it.
The problem is that $! is being treated too much like a global
variable and not enough like a lexical variable. Consider the
following example:
sub foo () {
try {
die MyMessage.new( 'key' => 'dang', 'vars' => {'from'=>'foo()'} );
};
if ($!) {
display_message( $! );
}
else {
display_message( MyMessage.new(
'key' => 'nuthin_wrong', 'vars' => {'from'=>'foo()'} ) );
}
}
sub display_message ( MyMessage $m ) {
try {
potential_problem(); # on problem, dies
};
if ($!) {
deal_with_it();
}
say "this message has a key {$m.key()}, is from {$m.vars{'from'}}";
}
This is a generified example of my Locale::KeyedText module and its examples.
Under the current system, a subroutine argument is an alias for the
container passed to it; in this case, $e is an alias for the $! that
was set in foo()'s try-block.
Unfortunately, the $! that is set or undeffed by
display_exception()'s try-block appears to be the same container as
foo()'s $!, which means that by the time say() is called, $e has been
wiped out.
Now, I can understand that consequence if the code instead looked like this:
my $*myerr = undef;
sub foo () {
try {
$*myerr = MyMessage.new( 'key' => 'dang', 'vars' => {'from'=>'foo()'} );
};
if ($*myerr) {
display_message ( $*myerr );
}
else {
display_message( MyMessage.new(
'key' => 'nuthin_wrong', 'vars' => {'from'=>'foo()'} ) );
}
}
sub display_message ( MyMessage $m ) {
try {
potential_problem(); # on problem, first sets $*myerr before death
};
if ($*myerr) {
deal_with_it();
}
say "this message has a key {$m.key()}, is from {$m.vars{'from'}}";
}
However, much like $_, I expect that exception handling done within a
called subroutine isn't going to mess up that subroutine's parent
scope, and consequently its own arguments.
Under the current system, I can keep $m preserved by either declaring
that argument with 'is copy', or having foo() explicitly copy its $!
to another lexical var which is then passed to display_exception(),
however, either of those is counter-intuitive and ugly.
First of all, declaring a subroutine's argument 'is copy' presumes
that either you are going to modify its value in the subroutine, or
you will be storing it in an object attribute or something and you
don't want to risk changes to it when the subroutine's caller
modifies the container it just passed.
But in the case of my example, the called subroutine is doing neither
of those things; no changes are made, and $m isn't kept beyond the
subroutine's execution.
Note that, while my examples don't use a CATCH block (which isn't yet
implemented in Pugs), you get the exception from $! or an alias
thereof anyway, so whether or not I used CATCH is factored out of the
discussion at hand here.
Now, I'm not sure exactly of the right solution for this, and perhaps
others such as autrijus can chime in with some help here, but the
current situation needs to be changed. Either $! becomes
semi-lexical, so a called sub's try block doesn't wipe out the $!
from a caller, or $! is treated special when used as a subroutine
argument and is automatically passed by copy, without the subroutine
signiture having to specify 'is copy'.
What I want to have happen is that display_message() works correctly
regardless of whether its argument came from $! or some other source,
without having to declare 'is copy' because of the possibility it is
from $!.
The big problem here is that almost any argument for any subroutine
could potentially come from $!, and it would be rediculous for them
all to be 'is copy' just to handle the possibility.
Nor is it reasonable for a caller to have to copy $! to another
variable before passing it to a subroutine just because that sub
*may* have its own try block ... a caller shouldn't have to know
about the implementation of what it called.
I welcome feedback on this issue and a positive, elegant resolution.
-- Darren Duncan