16

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.

w.k
  • 8,218
  • 4
  • 32
  • 55
  • The code simply dies when evaluating arguments before the non-existing function `try` is called. – nwellnhof Sep 18 '19 at 12:53
  • @nwellnhof So, as argument call for `try` (`call_a`) runs before call to `try` itself and dies during it, `try` is never tried. I tried `anything` instead of `try`, with same result. Your analysis seems correct. Submit it as an answer, please. – w.k Sep 18 '19 at 13:06
  • @nwellnhof Still, I made another version, when `call_a` randomly dies. And if it does die, `catch` block runs too. If `catch` dies too, then is still no errors. If `catch` does not `die`, we got error: `Can't locate object method "catch"`. It does not disprove your analysis, of course. It just illustrates, how you can cut yourself with this. – w.k Sep 18 '19 at 13:14
  • correction: 'And if it does not die' – w.k Sep 18 '19 at 13:32

1 Answers1

10

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.
zdim
  • 64,580
  • 5
  • 52
  • 81
  • You mention `METHODNAME PKGNAME LIST`, but the code uses `METHODNAME BLOCK LIST`. You might want the clarify that the latter is equivalent to `(do BLOCK)->METHODNAME(LIST)`. You see this for `catch` in the Deparse, but Deprase continued to use the indirect notation for `try`. – ikegami Sep 18 '19 at 22:38
  • In fact, because Deparse still uses an indirect method call, I'd avoid using it in the answer at all. [Suggested edit](https://pastebin.com/bRGZ8ymn) – ikegami Sep 18 '19 at 22:52
  • @ikegami Edited, thank you. Probably (way) too long/wordy but at least should be clear now – zdim Sep 19 '19 at 07:05
  • @zdim Thank you for such thorough explanation! – w.k Sep 19 '19 at 08:29
  • @w.k You are welcome and thanks for saying that :) Let me know if more/better is needed – zdim Sep 19 '19 at 16:15