The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Nice::Try - A real Try Catch Block Implementation Using Perl Filter

SYNOPSIS

    use Nice::Try;

    print( "Hello, I want to try\n" );
    # Try out {
    print( "this piece of code\n" );
    try 
    {
        # Not so sure }
        print( "I am trying!\n" );
        die( "Bye cruel world..." );
        # Never going to reach this
        return( 1 );
    }
    # Some comment
    catch( Exception $e ) {
        return( "Caught an exception \$e" );
    }
    # More comment with space too

    catch( $e ) {
        print( "Got an error: $e\n" );
    }
    finally
    {
        print( "Cleaning up\n" );
    }
    print( "Ok, then\n" );

When run, this would produce, as one would expect:

    Hello, I want to try
    this piece of code
    I am trying!
    Got an error: Bye cruel world... at ./some/script.pl line 18.
    Cleaning up
    Ok, then

VERSION

    v0.1.2

DESCRIPTION

Nice::Try is a lightweight implementation of Try-Catch exception trapping block using perl filter. It behaves like you would expect.

Here is a list of its distinctive features:

  • No routine to import like Nice::Try qw( try catch ). Just add use Nice::Try in your script

  • Properly report the right line number for the original error message

  • Allows embedded try-catch block within try-catch block, such as:

        use Nice::Try;
    
        print( "Wow, something went awry: ", &gotcha, "\n" );
    
        sub gotcha
        {
            print( "Hello, I want to try\n" );
            # Try out {
            CORE::say( 'this piece' );
            try 
            {
                # Not so sure }
                print( "I am trying!\n" );
                try
                {
                    die( "Bye cruel world..." );
                    return( 1 );
                }
                catch( $err )
                {
                    die( "Dying again with embedded error: '$err'" );
                }
            }
            catch( Exception $e ) {
                return( "Caught an exception \$e" );
            }
            catch( $e ) {
                try
                {
                    print( "Got an error: $e\n" );
                    print( "Trying something else.\n" );
                    die( "No really, dying out... with error: $e\n" );
                }
                catch( $err2 )
                {
                    return( "Returning from catch L2 with error '$err2'" );
                }
            }
            CORE::say( "Ok, then" );
        }
  • No need for semicolon on the last closing brace

  • It does not rely on perl regular expression, but instead uses PPI (short for "Perl Parsing Interface").

  • Variable assignment in the catch block works. For example:

        try
        {
            # Something or
            die( "Oops\n" );
        }
        catch( $funky_variable_name )
        {
            return( "Oh no: $funky_variable_name" );
        }
  • $@ is always available too

  • You can return a value from try-catch blocks, even with embedded try-catch blocks

  • It recognises @_ inside try-catch blocks, so you can do something like:

        print( &gotme( 'Jacques' ), "\n" );
    
        sub gotme
        {
            try
            {
                print( "I am trying my best $_[0]!\n" );
                die( "But I failed\n" );
            }
            catch( $some_reason )
            {
                return( "Failed: $some_reason" );
            }
        }

    Would produce:

        I am trying my best Jacques!
        Failed: But I failed

WHY USE IT?

There are quite a few implementations of try-catch blocks in perl, and they can be grouped in 4 categories:

1 Try-Catch as subroutines

For example Try::Tiny

2 Using Perl Filter

For example Nice::Try, Try::Harder

3 Using Devel::Declare

For example TryCatch

4 Others

For example Syntax::Keyword::Try

Group 1 requires the use of semi-colons like:

    try
    {
        # Something
    }
    catch
    {
        # More code
    };

It also imports the subroutines try and catch in your namespace.

And you cannot do exception variable assignment like catch( $err )

In group 2, Try::Harder does a very nice work, but relies on perl regular expression with Text::Balanced and that makes it susceptible to failure if the try-catch block is not written as it expects it to be. For example if you put comments between try and catch, it would not work anymore. This is because parsing perl is famously difficult. Also, it does not do exception variable assignment, or catch filtered based on exception class like:

    try
    {
        # Something
        die( Exception->new( "Failed!" ) );
    }
    catch( Exception $e )
    {
        # Do something if exception is an Exception class
    }

See "die" in perlfunc for more information on dying with an object.

Also Try::Harder will die if you use only try with no catch, such as:

    use Try::Harder;
    try
    {
        die( "Oops\n" );
    }
    # Will never reach this
    print( "Got here with $@\n" );

In this example, the print line will never get executed. With Nice::Try you can use try alone as an equivalent of "eval" in perlfunc and the $@ will be available too. So:

    use Nice::Try;
    try
    {
        die( "Oops\n" );
    }
    print( "Got here with $@\n" );

will produces:

    Got here with Oops

In group 3, TryCatch was working wonderfully, but was relying on Devel::Declare which was doing some esoteric stuff and eventually the version 0.006020 broke TryCatch and there seems to be no intention of correcting this breaking change.

In group 4, there is Syntax::Keyword::Try, which is a great alternative if you do not care about exception variable assignment or exception class filter. You can only use $@

So, Nice::Try is quite unique and fill the missing features, but because it is purely in perl and not an XS module, it is slower than XS module like Syntax::Keyword::Try, although I am not sure the difference would be noticeable.

FINALLY

Like with other language such as Java or JavaScript, the finally block will be executed even if the try or catch block contains a return statement.

This is useful to do some clean-up. For example:

    try
    {
        # Something worth dying
    }
    catch( $e )
    {
        return( "I failed: $e" );
    }
    finally
    {
        # Do some mop up
        # But here, would never be reached because catch already returned
    }

However, because this is designed for clean-up, it is called in void context, so any return statement there will not actually return anything back to the caller.

DEBUGGING

If you want to see the updated code produced, either call your script using Filter::ExtractSource like this:

    perl -MFilter::ExtractSource script.pl > updated_script.pl

or add use Filter::ExtractSource inside it.

In the updated script produced, you can add the line calling Nice::Try to:

    use Nice::Try no_filter => 1;

to avoid Nice::Try from filtering your script

If you want Nice::Try to produce human readable code, pass it the debug_code parameter like this:

    use Nice::Try debug_code => 1, debug 3;

And to have Nice::Try produce copious amount of debugging information, pass it the debug parameter like this:

    use Nice::Try debug => 3;

CREDITS

Credits to Stephen R. Scaffidi for his implementation of Try::Harder from which I borrowed some code.

AUTHOR

Jacques Deguest <jack@deguest.jp>

SEE ALSO

PPI, Filter::Util::Call, Try::Harder, Syntax::Keyword::Try

COPYRIGHT & LICENSE

Copyright (c) 2020 DEGUEST Pte. Ltd.

You can use, copy, modify and redistribute this package and associated files under the same terms as Perl itself.