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

Reply via email to