Perl is dieing

Question: Can we use Perl's eval construct in a "functional" way, in that die-or-no-die the construct evaluates to the same type of thing?

#!/usr/bin/env perl

use strict;
use warnings;

sub func1 {
    my ($die_or_not) = @_;
    my $res = eval {
        print "inside eval\n";
        if ($die_or_not) {
            die "doing a die";
        }
        return {
            status => 1,
            message => "made it",
        };
    }
    or do {
        return {
            status => 0,
            message => "died: " . $@,
        };
    };

    printf("STATUS: %d, MESSAGE: '%s'\n", $res->{status}, $res->{message});
    return "yes";
}

printf("without die: %s\n", func1(0));
printf("with die:    %s\n", func1(1));

Result:

inside eval
STATUS: 1, MESSAGE: 'made it'
without die: yes
inside eval
with die:    HASH(0x55d998e254d8)

Conclusion: Not that way :-/ What happened? The return inside the or do block is treated as a return from the func1 sub, whereas the return from the eval block is assigned to $res. This means our whole eval-or-do doesn't evaluate to the same type of thing - in fact it's evaluation completely messes up program flow. Well, what about this weird way:

#!/usr/bin/env perl

use strict;
use warnings;

sub func1 {
    my ($die_or_not) = @_;
    return eval {
        print "inside eval\n";
        if ($die_or_not) {
            die "doing a die";
        }
        return {
            status => 1,
            message => "made it",
        };
    }
    or do {
        return {
            status => 0,
            message => "died: " . $@,
        };
    };
}

my $wo = func1(0);
my $wi = func1(1);
printf("without die: %d:%s\n", $wo->{status}, $wo->{message});
printf("with die:    %d:%s\n", $wi->{status}, $wi->{message});

Result:

Possible precedence issue with control flow operator at test.pl line 23.
inside eval
inside eval
without die: 1:made it
Use of uninitialized value in printf at test.pl line 29.
Use of uninitialized value in printf at test.pl line 29.
with die:    0:

Nope, not at all. It turns out that perl does not like a return from an eval, even though its happy to assign a variable from it. We can simulate what we want by writing a small wrapper called _evaly:

sub _evaly {
    my ($code, $err) = @_;
    my $res = eval {
        return $code->();
    }
    or do {
        return $err->($@);
    };
    return $res;
}

This takes two code-refs. The first one is executed inside the eval, and if there is no die its return value is the result of the expression. If that code dies the $err code-ref is executed and it's return value is the value of the expression. In this way the final result of the expression can be made uniform for the die and not-die case:

sub func1 {
    my ($die_or_not) = @_;
    return _evaly(sub {
        if ($die_or_not) {
            die "doing a die";
        }
        return {
            status => 1,
            message => "made it",
        };
    }, sub {
        my ($err) = @_;
        return {
            status => 0,
            message => "died: " . $@,
        };
    });
}

my $wo = func1(0);
my $wi = func1(1);
printf("without die: %d:%s\n", $wo->{status}, $wo->{message});
printf("with die:    %d:%s\n", $wi->{status}, $wi->{message});

Result:

without die: 1:made it
with die:    0:died: doing a die at test.pl line 21.

Is there a better way?? What about Try::Tiny:

This is unlike TryCatch which provides a nice syntax and avoids adding another call stack layer, and supports calling return from the try block to return from the parent subroutine.

Sounds good, lets try:

#!/usr/bin/env perl

use strict;
use warnings;
use Try::Tiny;

sub func1 {
    my ($die_or_not) = @_;
    return try {
        if ($die_or_not) {
            die "doing a die";
        }
        return {
            status => 1,
            message => "made it",
        };
    }
    catch {
        return {
            status => 0,
            message => "died: " . $_,
        };
    };
}

my $wo = func1(0);
my $wi = func1(1);
printf("without die: %d:%s\n", $wo->{status}, $wo->{message});
printf("with die:    %d:%s\n", $wi->{status}, $wi->{message});

Result:

without die: 1:made it
with die:    0:died: doing a die at test.pl line 11.

Horray! That looks nice! Now let's see if we can construct something that captures this unified thing inline:

sub func1 {
    my ($die_or_not) = @_;
    my $res = try {
        if ($die_or_not) {
            die "doing a die";
        }
        return {
            status => 1,
            message => "made it",
        };
    }
    catch {
        chomp;
        return {
            status => 0,
            message => "died: " . $_,
        };
    };
    $res->{extra} = "hello";
    return $res;
}

In both cases this correctly adds the "extra" info the the return structure. The return inside the try and catch blocks do not return from the func1 sub! So in summary, the pattern is like so:

use Try::Tiny;

my $result = try {
    # Something that may or may not die
    return <your type>;
}
catch {
    # $_ has the error
    return <your type>;
};

And now $result will be <your type> die-or-no-die! Can this be input to a function??

sub message {
    my ($r) = @_;
    printf("result: %d: %s\n", $r->{status}, $r->{message});
}

sub func1 {
    my ($die_or_not) = @_;
    message(try {
        if ($die_or_not) {
            die "doing a die";
        }
        return {
            status => 1,
            message => "made it",
        };
    }
    catch {
        chomp;
        return {
            status => 0,
            message => "died: " . $_,
        };
    });
}

func1(0);
func1(1);

Result:

result: 1: made it
result: 0: died: doing a die at test.pl line 16.

Holy heck, that works perfectly! Why would you want this? Imagine you want to update the status of a record:

sub update {
    my ($u, $r) = @_;
    $u->set_status($r->{status});
    $u->set_message($r->{message});
    $u->save;
}

sub try_to_do_something {
    my ($status_record, $something) = @_;
    update($status_record, try {
        $something->doit;
        return {
            status => 1,
            message => "made it",
        };
    }
    catch {
        chomp;
        return {
            status => 0,
            message => "died: " . $_,
        };
    });
}

See how try-catch has become a composable thing with a return value, just like any other sub! With traditional eval that update flow would look like this:

sub try_to_do_something {
    my ($status_record, $something) = @_;
    my $status;
    my $message;
    eval {
        $something->doit;
        $status = 1;
        $message = "made it";
        1;
    }
    or do {
        my $err = $@;
        chomp $err;
        $status = 0;
        $message = "died: " . $err;
    };
    $u->set_status($status);
    $u->set_message($message);
    $u->save;
}

This sucks because:

State management

The $status and $message variables are declared undefined before they are used. If you have an eval block with many of lines of code, and an or do with many lines of code, you can easily visually loose track of what state variables are in flight. With try-catch you only have to look at return expressions to understand what the result is. Ok, you could pre-declare them to a value, but then what values? You could declare $status to 99 which would mean: "I forgot to manage state properly and accidentially broke the code".

Not composable

Updating your status record is not tightly coupled with your try_to_do_something sub. You could easily separate these things, and in normal flow just pipe the output of one func into the input of another. But for testing you can just call these subs separately.

During refactoring

You might have a very large function (as I did) doing all sorts of things with many state variables in flight. You can't safely refactor the entire function in one go to use eval better, but you can easily introduce a try-catch expression to better declare what the "output" of your doit attempt should be!