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

Also since version 1.0.0, Nice::Try{.perl-module} is extended context aware:

    use Want; # an awesome module which extends wantarray
    sub info
    {
        my $self = shift( @_ );
        try
        {
            # Do something
            if( want('OBJECT') )
            {
                return( $self );
            }
            elsif( want('CODE') )
            {
                # dummy code ref for example
                return( sub{ return( $name ); } );
            }
            elsif( want('LIST') )
            {
                return( @some_data );
            }
            elsif( want('ARRAY') )
            {
                return( \@some_data );
            }
            elsif( want('HASH') )
            {
                return({ name => $name, location => $city });
            }
            elsif( want('REFSCALAR') )
            {
                return( \$name );
            }
            elsif( want('SCALAR' ) )
            {
                return( $name ); # regular string
            }
            elsif( want('VOID') )
            {
                return;
            }
        }
        catch( $e )
        {
            $Logger->( "Caught exception: $e" );
        }
    }

    # regular string context
    my $name = $o->info;
    # code context
    my $name = $o->info->();
    # list context like wantarray
    my @data = $o->info;
    # hash context
    my $name = $o->info->{name};
    # array context
    my $name = $o->info->[2];
    # object context
    my $name = $o->info->another_method;
    # scalar reference context
    my $name = ${$o->info};

And you also have granular power in the catch block to filter which exception to handle. See more on this in "EXCEPTION CLASS"{.perl-module}

    try
    {
        die( Exception->new( "Arghhh" => 401 ) );
    }
    # can also write this as:
    # catch( Exception $oopsie where { $_->message =~ /Arghhh/ && $_->code == 500 } )
    catch( $oopsie isa Exception where { $_->message =~ /Arghhh/ && $_->code == 500 } )
    {
        # Do something to deal with some server error
    }
    catch( $oopsie isa Exception where { $_->message =~ /Arghhh/ && $_->code == 401 } )
    {
        # should reach here.
    }
    catch( $oh_well isa("Exception") ) # or you can also write catch( Exception $oh_well )
    {
        # Default using another way to filter by Exception
    }
    catch( $oopsie where { /Oh no/ } )
    {
        # Do something based on the value of a simple error; not an exception class
    }
    # Default
    catch( $default )
    {
        print( "Unknown error: $default\n" );
    }

VERSION

    v1.3.1

DESCRIPTION

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

Here is a list of its distinctive features:

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{.perl-module}

2 Using Perl Filter

: For example Nice::Try{.perl-module}, Try::Harder{.perl-module}

3 Using Devel::Declare{.perl-module}

: For example TryCatch{.perl-module}

4 Others

: For example Syntax::Keyword::Try{.perl-module} and now perl with version 5.34.0 using experimental feature{.perl-module}.

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{.perl-module} does a very nice work, but relies on perl regular expression with Text::Balanced{.perl-module} 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{.perl-module} for more information on dying with an object.

Also Try::Harder{.perl-module} 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{.perl-module} you can use try alone as an equivalent of "eval" in perlfunc{.perl-module} 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{.perl-module} was working wonderfully, but was relying on Devel::Declare{.perl-module} which was doing some esoteric stuff and eventually the version 0.006020 broke TryCatch{.perl-module} and there seems to be no intention of correcting this breaking change. Besides, Devel::Declare{.perl-module} is now marked as deprecated and its use is officially discouraged.

TryCatch{.perl-module} does not support any finally block.

In group 4, there is Syntax::Keyword::Try{.perl-module}, which is a great alternative if you do not care about exception class filter (it supports variable assignment since 2020-08-01 with version 0.18).

Although, the following script would not work under Syntax::Keyword::Try{.perl-module} :

    BEGIN
    {
        use strict;
        use warnings;
        use Syntax::Keyword::Try;
    };

    {
        &callme();
    }

    sub callme
    {
        try {
            print( "Hello there\n" );
        }
        catch ($e) {
            print( "Got an error: $e\n" );
        }
    }

This will trigger the following error:

    syntax error at ./test.pl line 18, near ") {"
    syntax error at ./test.pl line 21, near "}"
    Execution of ./test.pl aborted due to compilation errors.

That is because Syntax::Keyword::Try{.perl-module} expects to be used outside of a BEGIN block like this:

    use strict;
    use warnings;
    use Syntax::Keyword::Try;

    # Rest of the script, same as above

Of course, with Nice::Try{.perl-module}, there is no such constraint. You can "use" in perlfunc{.perl-module} Nice::Try{.perl-module} inside or outside of a BEGIN block indistinctively.

Since perl version 5.33.7{.perl-module} and now in perl v5.34.0{.perl-module} you can use the try-catch block using an experimental feature which may be removed in future versions, by writing:

    use feature 'try'; # will emit a warning this is experimental

This new feature supports try-catch block and variable assignment, but no exception class, nor support for finally block, so you can do:

    try
    {
        # Oh no!
        die( "Argh...\n" );
    }
    catch( $oh_well )
    {
        return( $self->error( "Something went awry: $oh_well" ) );
    }

But you cannot do:

    try
    {
        # Oh no!
        die( MyException->new( "Argh..." ) );
    }
    catch( MyException $oh_well )
    {
        return( $self->error( "Something went awry with MyException: $oh_well" ) );
    }
    # No support for 'finally' yet in perl version 5.33.7
    finally
    {
        # do some cleanup here
    }

Also, the use feature 'try' expression must be in the relevant block where you use try-catch. You cannot just put it in your BEGIN block at the beginning of your script. If you have 3 subroutines using try-catch, you need to put use feature 'try' in each of them. See perl documentation on lexical effect{.perl-module} for more explanation on this.

It is probably a matter of time until this is fully implemented in perl as a regular non-experimental feature.

See more information about perl's featured implementation of try-catch in perlsyn{.perl-module}

So, Nice::Try{.perl-module} is quite unique and fills the missing features, and since it uses XS modules for a one-time filtering, it is quite fast.

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
        # This would be reached even if catch already returned
        # Putting return statement here does not actually return anything.
        # This is only for clean-up
    }

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.

CATCHING OR NOT CATCHING?

Nice::Try{.perl-module} can be used with a single try block which will, in effect, behaves like an eval and the special variable $@ will be available as always.

    try
    {
        die( "Oh no, something went wrong!\n" );
    }
    print( "Got here with $@\n" );

or even:

    try
    {
        die( "Oh no, something went wrong!\n" );
    }
    catch( $e ); # Not very meaningful, but it will work
    print( "Got here with $@\n" );

However, if you decide to catch class exceptions, make sure to add a default catch( $e ). For example:

    try
    {
        die( MyException->new( "Oh no" ) );
    }
    print( "Got here with $@\n" );

will work and print will display "Got here with Oh no". However:

    try
    {
        die( MyException->new( "Oh no" ) );
    }
    catch( Some::Exception $e )
    {
        # won't reach here
    }

will make your process die because of the exception not being caught, thus you might want to do instead:

    try
    {
        die( MyException->new( "Oh no" ) );
    }
    catch( Some::Exception $e )
    {
        # won't reach here
    }
    catch( $default )
    {
        print( "Got you! Error was: $default\n" );
    }

And the last catch will catch the exception.

Since, try-catch block can be nested, the following would work too:

    try
    {
        try
        {
            die( MyException->new( "Oh no" ) );
        }
        catch( Some::Exception $e )
        {
            # won't reach here
        }
    }
    catch( MyException $e )
    {
        print( "Got you! MyException was: $e\n" );
    }
    # to play it safe
    catch( $e )
    {
        # do something about it
    }

EXCEPTION CLASS

As mentioned above, you can use class when raising exceptions and you can filter them in a variety of ways when you catch them.

Here are your options (replace Exception::Class with your favorite exception class):

1. catch( Exception::Class $error_variable ) { }

:

2. catch( Exception::Class $error_variable where { $condition } ) { }

: Here $condition could be anything that fits in a legitimate perl block, such as:

        try
        {
            die( Exception->new( "Oh no!", { code => 401 } ) );
        }
        catch( Exception $oopsie where { $_->code >= 400 && $_->code <= 499 })
        {
            # some more handling here
        }

In the condition block `$_` will always be made available and will
correspond to the exception object thrown, just like `$oopsie` in
this example. `$@` is also available with the exception object as
its value.

3. catch( $e isa Exception::Class ) { }

: This is a variant of the catch( Exception::Class $e ) {} form

4. catch( $e isa('Exception::Class') ) { }

: A variant of the one above if you want to use single quotes.

5. catch( $e isa("Exception::Class") ) { }

: A variant of the one above if you want to use double quotes.

6. catch( $e isa Exception::Class where { $condition } ) { }

:

7. catch( $e isa('Exception::Class') where { $condition } ) { }

:

8. catch( $e isa("Exception::Class") where { $condition } ) { }

:

9. catch( $e where { $condition } ) { }

: This is not a class exception catching, but worth mentioning. For example:

        try
        {
            die( "Something bad happened.\n" );
        }
        catch( $e where { /something bad/i })
        {
            # Do something about it
        }
        catch( $e )
        {
            # Default here
        }

LOOPS

Since version v0.2.0 Nice::Try{.perl-module} supports the use of flow control keywords such as next, last and redo inside try-catch blocks. For example:

    my @names = qw( John Jack Peter Paul Mark );
    for( $i..$#names )
    {
        try
        {
            next if( $i == 2 );
            # some more code...
        }
        catch( $e )
        {
            print( "Got exception: $e\n" );
        }
    }

It also works inside the catch block or inside the continue block:

    while( defined( my $product = $items->[++$i] ) )
    {
        # Do something
    }
    continue
    {
        try
        {
            if( $product->region eq 'Asia' )
            {
                push( @asia, $product );
            }
            else
            {
                next;
            }
        }
        catch( $e )
        {
            $log->( "An unexpected error has occurred. Is $product an object? $e" );
            last;
        }
    }

Control flow with labels also work

    ELEM: foreach my $n ( @names )
    {
        try
        {
            $n->moveAfter( $this );
            next ELEM if( $n->value == 1234567 );
        }
        catch( $oops )
        {
            last ELEM;
        }
    }

However, if you enclose a try-catch block inside another block, use of next, last or redo will silently not work. This is due to perl control flow. See perlsyn{.perl-module} for more information on this. For example, the following would not yield the desired outcome:

    ELEM: foreach my $n ( @names )
    {
        { # <--- Here is the culprit
            try
            {
                $n->moveAfter( $this );
                # This next statement will not do anything.
                next ELEM if( $n->value == 1234567 );
            }
            catch( $oops )
            {
                # Neither would this one.
                last ELEM;
            }
        }
    }

CONTEXT AWARENESS

Nice::Try{.perl-module} provides a high level of granularity about the context in which your subroutine was called.

Normally, you would write something like this, and it works as always:

    sub info
    {
        try
        {
            # do something here
            if( wantarray() )
            {
                return( @list_of_values );
            }
            # caller just want a scalar
            elsif( defined( wantarray() ) )
            {
                return( $name );
            }
            # otherwise if undefined, it means we are called in void context, like:
            # $o->info; with no expectation of return value
        }
        catch( $e )
        {
            print( "Caught an error: $e\n" );
        }
    }

The above is nice, but how do you differentiate cases were your caller wants a simple returned value and the one where the caller wants an object for chaining purpose, or if the caller wants an hash or array reference in return?

For example:

    my $val = $o->info->[2]; # wants an array reference
    my $val = $o->info->{name} # wants an hash reference
    # etc...

Now, you can do the following:

    use Want; # an awesome module which extends wantarray
    sub info
    {
        my $self = shift( @_ );
        try
        {
            # Do something
            # 
            # same as wantarray() == 1
            if( want('LIST') )
            {
                return( @some_data );
            }
            # same as: if( defined( wantarray() ) && !wantarray() )
            elsif( want('SCALAR' ) )
            {
                return( $name ); # regular string
            }
            # same as if( !defined( wantarray() ) )
            elsif( want('VOID') )
            {
                return;
            }
            # For the other contexts below, wantarray is of no help
            if( want('OBJECT') )
            {
                return( $obj ); # useful for chaining
            }
            elsif( want('CODE') )
            {
                # dummy code ref for example
                return( sub{ return( $name ); } );
            }
            elsif( want('ARRAY') )
            {
                return( \@some_data );
            }
            elsif( want('HASH') )
            {
                return({ name => $name, location => $city });
            }
        }
        catch( $e )
        {
            $Logger->( "Caught exception: $e" );
        }
    }

Thus this is particularly useful if, for example, you want to differentiate if the caller just wants a return string, or an object for chaining.

"wantarray" in perlfunc{.perl-module} would not know the difference, and other try-catch implementation would not let you benefit from using Want{.perl-module}.

For example:

    my $val = $o->info; # simple regular scalar context; but...
    # here, we are called in object context and wantarray is of no help to tell the difference
    my $val = $o->info->another_method;

Other cases are:

    # regular string context
    my $name = $o->info;
    # list context like wantarray
    my @data = $o->info;

    # code context
    my $name = $o->info->();
    # hash context
    my $name = $o->info->{name};
    # array context
    my $name = $o->info->[2];
    # object context
    my $name = $o->info->another_method;

See Want{.perl-module} for more information on how you can benefit from it.

Currently lvalues are no implemented and will be in future releases. Also note that Want{.perl-module} does not work within tie-handlers. It would trigger a segmentation fault. Nice::Try{.perl-module} detects this and disable automatically support for Want{.perl-module} if used inside a tie-handler, reverting to regular "wantarray" in perlfunc{.perl-module} context.

Also, for this rich context awareness to be used, obviously try-catch would need to be inside a subroutine, otherwise there is no rich context other than the one the regular "wantarray" in perlfunc{.perl-module} provides.

This is particularly true when running within an Apache modperl handler which has no caller. If you use Nice::Try{.perl-module} in such handler, it will kill Apache process, so you need to disable the use of Want{.perl-module}, by calling:

    use Nice::Try dont_want => 1;

When there is an update to correct this bug from Want{.perl-module}, I will issue a new version.

The use of Want{.perl-module} is also automatically disabled when running under a package that use overloading.

LIMITATIONS

Currently, the only known limitation is when one use experimental subroutine attributes inside a try-catch block on an anonymous subroutine. For example:

    use strict;
    use warnings;
    use experimental 'signatures';
    use Nice::Try;

    sub test { 1 }

    sub foo ($f = test()) { 1 }

    try {
        my $k = sub ($f = foo()) {}; # <-- this sub routine attribute inside try-catch block will disrupt Nice::Try and make it fail.
        print( "worked\n" );
    }
    catch($e) {
        warn "caught: $e";
    }

    __END__

instead, do not use experimental subroutine attributes inside try-catch block:

    use strict;
    use warnings;
    use experimental 'signatures';
    use Nice::Try;

    sub test { 1 }

    sub foo ($f = test()) { 1 }

    try {
        my $k = sub {}; # <-- Now it works normally
        print( "worked\n" );
    }
    catch($e) {
        warn "caught: $e";
    }

    __END__

DEBUGGING

And to have Nice::Try{.perl-module} save the filtered code to a file, pass it the debug_file parameter like this:

    use Nice::Try debug_file => './updated_script.pl';

You can also call your script using Filter::ExtractSource{.perl-module} 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{.perl-module} to:

    use Nice::Try no_filter => 1;

to avoid Nice::Try{.perl-module} from filtering your script

If you want Nice::Try{.perl-module} to produce human readable code, pass it the debug_code parameter like this:

    use Nice::Try debug_code => 1;

CLASS FUNCTIONS

The following class functions can be used.

implement

    my $new_code = Nice::Try->implement( $perl_code );
    eval( $new_code );

Provided with a perl code having one or more try-catch blocks and this will return a perl code converted to support try-catch blocks.

This is designed to be used for perl code you store, such as subroutines dynamically loaded or eval'ed.

For example:

    my $code = Nice::Try->implement( <<EOT );
    sub $method
    {
        my \$self = shift( \@_ );
        try
        {
            # doing something that may die here
        }
        catch( \$e )
        {
            return( \$self->error( "Oops: \$e ) );
        }
    }
    EOT

You can also pass an optional hash or hash reference of options to "implement"{.perl-module} and it will be used to instantiate a new Nice::Try{.perl-module} method. The options accepted are the same ones that can be passed when using use Nice::Try

CREDITS

Credits to Stephen R. Scaffidi for his implementation of Try::Harder{.perl-module} from which I borrowed some code.

AUTHOR

Jacques Deguest <jack@deguest.jp{classes="ARRAY(0x5630cc75f2c0)"}>

SEE ALSO

PPI{.perl-module}, Filter::Util::Call{.perl-module}, Try::Harder{.perl-module}, Syntax::Keyword::Try{.perl-module}, Exception::Class{.perl-module}

JavaScript implementation of nice-try{.perl-module}

COPYRIGHT & LICENSE

Copyright (c) 2020-2021 DEGUEST Pte. Ltd.

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