Domanda

My question is similar to: Is it possible for a Perl subroutine to force its caller to return? but I need procedural method.

I want to program some message procedure with return, example essential code:

sub PrintMessage {
    #this function can print to the screen and both to logfile
    print "Script message: $_[0]\n";
}

sub ReturnMessage {
    PrintMessage($_[0]);
    return $_[2];  #  <-- we thinking about *this* return
}

sub WorkingProc {
    PrintMessage("Job is started now");
    #some code
    PrintMessage("processed 5 items");

    # this should return from WorkingProc with given exitcode
    ReturnMessage("too many items!",5) if $items>100;

    #another code
    ReturnMessage("time exceded!",6) if $timespent>3600;
    PrintMessage("All processed succesfully");
    return 0;
}

my $ExitCode=WorkingProc();
#finish something
exit $ExitCode

Idea is, how to use return inside ReturnMessage function to exit with specified code from WorkingProc function? Notice, ReturnMessage function is called from many places.

È stato utile?

Soluzione

This isn't possible. However, you can explicitly return:

sub WorkingProc {
    PrintMessage("Job is started now");
    ...
    PrintMessage("processed 5 items");

    # this returns from WorkingProc with given exitcode
    return ReturnMessage("to much items!", 5) if $items > 100;

    ...
    return ReturnMessage("time exceded!", 6) if $timespent > 3600;
    PrintMessage("All processed succesfully");
    return 0;
}

A sub can have any number of return statements, so this isn't an issue.

Such a solution is preferable to hacking through the call stack, because the control flow is more obvious to the reader. What you were dreaming of was a kind of GOTO, which most people not writing C or BASIC etc. have given up 45 years ago.

Your code relies on exit codes to determine errors in subroutines. *Sigh*. Perl has an exception system which is fairly backwards, but still more advanced than that.

Throw a fatal error with die "Reason", or use Carp and croak "Reason". Catch errors with the Try::Tiny or TryCatch modules.

sub WorkingProc {
    PrintMessage("Job is started now");
    ...
    PrintMessage("processed 5 items");

    # this should return from WorkingProc with given exitcode
    die "Too much items!" if $items > 100;

    ...
    die "Time exceeded" if $timespent > 3600;
    PrintMessage("All processed succesfully");
    return 0;
}

WorkingProc();

If an error is thrown, this will exit with a non-zero status.

Altri suggerimenti

The approach that springs to mind for non-local return is to throw an exception (die) from the innermost function.

You'll then need to have some wrapping code to handle it at the top level. You could devise a set of utility routines to automatically set that up.

Using Log::Any and Log::Any::Adapter in conjunction with Exception::Class allow you to put all the pieces together with minimum fuss and maximum flexibility:

#!/usr/bin/env perl

package My::Worker;
use strict; use warnings;

use Const::Fast;
use Log::Any qw($log);

use Exception::Class (
    JobException => { fields => [qw( exit_code )] },
        TooManyItemsException => {
            isa => 'JobException',
            description => 'The worker was given too many items to process',
        },
        TimeExceededException => {
            isa => 'JobException',
            description => 'The worker spent too much time processing items',
        },
);

sub work {
    my $jobid = shift;
    my $items = shift;

    const my $ITEM_LIMIT => 100;
    const my $TIME_LIMIT => 10;

    $log->infof('Job %s started', $jobid);

    shift @$items for 1 .. 5;
    $log->info('Processed 5 items');

    if (0.25 > rand) {
        # throw this one with 25% probability
        if (@$items > $ITEM_LIMIT) {
            TooManyItemsException->throw(
                error => sprintf(
                    '%d items remain. Limit is %d.',
                    scalar @$items, $ITEM_LIMIT,
                ),
                exit_code => 5,
            );
        }
    }

    { # simulate some work that might take more than 10 seconds
        local $| = 1;
        for (1 .. 40) {
            sleep 1 if 0.3 > rand;
            print '.';
        }
        print "\n";
    }
    my $time_spent = time - $^T;
    ($time_spent > $TIME_LIMIT) and
        TimeExceededException->throw(
            error => sprintf (
                'Spent %d seconds. Limit is %d.',
                $time_spent, $TIME_LIMIT,
            ),
            exit_code => 6);
    $log->info('All processed succesfully');
    return;
}

package main;

use strict; use warnings;
use Log::Any qw( $log );
use Log::Any::Adapter ('Stderr');

eval { My::Worker::work(exceptional_job => [1 .. 200]) };
if (my $x = JobException->caught) {
    $log->error($x->description);
    $log->error($x->error);
    exit $x->exit_code;
}

Sample output:

Job exceptional_job started
Processed 5 items
........................................
The worker spent too much time processing items
Spent 12 seconds. Limit is 10.

or

Job exceptional_job started
Processed 5 items
The worker was given too many items to process
195 items remain. Limit is 100.
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top