Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does "try" not cause an undefined subroutine error?

Tags:

perl

A couple of times I've ran into the situation where I've forgotten to load the Try::Tiny module in my script and I've still used its try-catch block, like this:

#!/usr/bin/env perl

use strict; 
use warnings;

try {
  call_a( 'x' );
} catch {
  die "ACTUALLY die $_";
};


sub call_a {
  die "Yes, I will";
}

For some reason, the script works fine without giving any hints that there is no try. No Undefined subroutine errors. This makes me wonder why my raised exceptions are not caught.

Why does this work silently, without an error?

EDIT

I looked into symbol table as well:

say "$_: %main::{ $_ }" for keys %main::; 

and found there no try. Also I tried to call it as main::try in the script above, and it caused also no errors.

like image 393
w.k Avatar asked Sep 18 '19 09:09

w.k


1 Answers

This is due to the indirect-object syntax, and is a more elaborate variation on this example.

The "indirect object notation" allows code

PackageName->method(@args);

to be written as

method PackageName @args;

So the "try" and "catch" words don't matter. The interesting bit here is the more involved and extended syntax, with two parts, each in this indirect object notation.

The code in question in fact has method BLOCK LIST form, but that also goes by indirect object syntax into (do BLOCK)->method(LIST), where do BLOCK needs to produce a name of a package or a blessed (object) reference for a meaningful method call. This is seen below in Deparse output.

Using B::Deparse compiler backend (via O module) on this code

use strict; 
use warnings;
use feature 'say';

try   { call_a( 'x' ) } 
catch { 
    die "ACTUALLY die";
    #say "NO DONT die";
};

sub call_a { 
    die "Yes it dies";
    #say "no die";
}

as perl -MO=Deparse script.pl should show a very close approximation of what runs:

use warnings;
use strict;
use feature 'say';
try {
    call_a('x')
} do {
    die 'ACTUALLY die'
}->catch;
sub call_a {
    use warnings;
    use strict;
    use feature 'say';
    die 'Yes it dies';
}
undef_sub.pl syntax OK

The nested indirect object syntax is apparently too much for Deparse which still leaves method BLOCK LIST form in the output. The equivalent code can be spelled out as

(do { call_a('x') })->try( (do { die("ACTUALLY die") })->catch() );

what in this case is more simply

call_a('x')->try( die("ACTUALLY die")->catch() );

Thus the original code is interpreted as valid syntax (!) and it is the contents of the block after try (call_a('x')) that runs first --- so the program dies and never gets to go for the "method" try.

It gets more interesting if we change the example to

use strict;
use warnings;
use feature 'say';

try   { call_a( 'x' ) }
catch {
    #die "ACTUALLY die"; 
    say "NO DONT die";
};

sub call_a {
    #die "Yes it dies";
    say "no die";
}

with no die-ing anywhere. Run it with -MO=Deparse to see

use warnings;
use strict;
use feature 'say';
try {
    call_a('x')
} (catch {
    say 'NO DONT die'
} );
sub call_a {
    use warnings;
    use strict;
    use feature 'say';
    say 'no die';
}
undef_sub.pl syntax OK

which is now in a straight-up method {} args syntax (with args itself shown by Deparse in an indirect object notation as well). The equivalent code is

call_a('x')->try( say("NO DONT die")->catch() );

where first the call_a() goes and, after it returns, then the code for the argument list in the try method call runs next. We aren't running into a die and an actual run goes as

no die
NO DONT die
Can't call method "catch" without a package or object reference at ...

So now a problem with the method "catch" does come up.

Thanks to ikegami for comments


If the block above were to return a name of a package (or object reference) which does have a method catch then the try would finally be attempted as well

use strict; 
use warnings;
use feature 'say';

BEGIN {
    package Catch;
    sub catch { say "In ", (caller(0))[3] };
    $INC{"Catch.pm"} = 1;
};

use Catch;

try   { call_a( 'x' ) } 
catch { 
    say "NO DONT die";
    "Catch";
};

sub call_a { say "no die" }

Now we have the equivalent

call_a('x')->try( do { say("NO DONT die"); 'Catch' }->catch() );

with the output

no die
NO DONT die
In Catch::catch
Can't call method "try" without a package or object reference at undef_sub.pl line 14.
like image 104
zdim Avatar answered Nov 11 '22 07:11

zdim