in reply to Exiting eval via next: is that so bad?

I happen to have a use case (and you can argue that it might be best refactored and I may just agree with you but for now, it has to be this way) where we have a couple eval closures within a larger loop and there are times we want to call "next" but within the "eval"s.

I was getting the same error as the OP when I found this question/thread. I then tried the double curly version of "eval" but it doesn't seem to handle $@ the way I want/expect so I tried something else:

#!/usr/bin/perl use strict; use warnings; my @times = (2, 4, 3, 1, 2, 5); select((select(STDOUT), $|=1)[0]); TIME: for my $time_to_wait (@times) { print "Time to wait is: $time_to_wait\n"; local $@; eval { # Don't handle even numbers. die '__NOT_FATAL__' if !($time_to_wait % 2); this_will_die_if_true($time_to_wait); 1; } or do { if($@ && $@ =~ /__NOT_FATAL__/) { next TIME; } print "Something went boom! ($@)\n"; }; print "Sleeping for $time_to_wait seconds ... "; sleep $time_to_wait; print "Done.\n"; } exit 0; sub this_will_die_if_true { my ($arg) = @_; $arg //= 0; $arg && die "We died!"; return; }

This does not produce the dreaded "Exiting eval via next at test.pl line 17." type errors and works the way I want but I'm reluctant to use this in production as it just seems, well...icky somehow. Thoughts?

-s1m0n-

Replies are listed 'Best First'.
Re^2: Exiting eval via next: is that so bad?
by haukex (Archbishop) on Jun 30, 2019 at 08:46 UTC
    but I'm reluctant to use this in production as it just seems, well...icky somehow.

    I would be very reluctant too. I'm confused by the example though - why not move the next TIME if !($time_to_wait % 2); outside of the eval? I guess this code might just be representative of some larger piece of code - in that case, isn't it possible to restrict the scope of the eval to just where it is needed, around the code that you don't want to be fatal, e.g. eval { this_will_die_if_true($time_to_wait); 1 } or do { ... };?

      Sorry, I can see why my example is confusing. Here's a slightly better one, more true to the production code:

      #!/usr/bin/perl use strict; use warnings; use Scalar::Util qw(looks_like_number); my @times = (2, 4, 3, 1, 3.5, 2, 5); select((select(STDOUT), $|=1)[0]); TIME: for my $time_to_wait (@times) { print "Time to wait is: $time_to_wait\n"; local $@; eval { # Skip even numbers. die '__NOT_FATAL__' if !($time_to_wait % 2); verify_number($time_to_wait); # Skip numbers less than 3. die '__NOT_FATAL__' if ($time_to_wait < 3); verify_time($time_to_wait); 1; } or do { if($@ && $@ =~ /__NOT_FATAL__/) { next TIME; } print "Something went boom! ($@)\n"; }; print "Sleeping for $time_to_wait seconds ... "; sleep $time_to_wait; print "Done.\n"; } exit 0; sub verify_number { my ($arg) = @_; die "We died!" if !defined $arg; looks_like_number($arg) or die "We died!"; return; } sub verify_time { my ($arg) = @_; die "We died!" if !defined $arg; # Floats not allowed. int($arg) == ($arg / 1) or die "We died!"; return; }

      Now imagine several more "verify" type calls within that same eval. Granted, it could be re-written to use a bunch more separate eval calls thus potentially allowing next calls outside/between them but in production those function calls are all very related and would die in similar ways. So I feel it comes down to what's more simple, cleaner? I think I might give the undermentioned Syntax::Keyword::Try package a try (swidt) since it does seem to support exiting via "next" calls.

      -s1m0n-
        Now imagine several more "verify" type calls within that same eval.

        Yeah, I figured it was something like that. Personally I still wouldn't use that "eval/die with a fixed string" pattern, and instead maybe wrap each call in an eval, or to make it look a little nicer, perhaps Try::Tiny. Of course, this being Perl, other ways come to mind :-) Say you've got a bunch of verify_* functions that you can't change and whose errors are fatal, but you want nonfatal versions of them:

        use warnings; use strict; # UUT use Scalar::Util qw/looks_like_number/; sub verify_number { my ($arg) = @_; die "not a number" if !defined $arg; looks_like_number($arg) or die "not a number"; return; } # wrap the funcs my @funcs = qw/ verify_number /; for my $f (@funcs) { my $orig = \&{$f}; my $wrapped = sub { eval { $orig->(@_); 1 } }; { no strict 'refs'; *{$f.'_nf'} = $wrapped } } use Test::More tests => 4; # helper sub exception (&) { eval { shift->(); 1 } ? undef : ($@ || die "\$@ was false") } ok !exception { verify_number("3.14") }; like exception { verify_number("foo") }, qr/\bnot a number\b/i; ok verify_number_nf("3.14"); ok !verify_number_nf("foo");

        Of course you don't need to install the wrapped versions into the symbol table, a hash would work fine too.

Re^2: Exiting eval via next: is that so bad?
by jdporter (Paladin) on Jun 27, 2019 at 13:26 UTC

    You gotta do what you gotta do. I use this pattern in C++ programming (and of course Perl programming) a fair bit... but not excessively. :-)

Re^2: Exiting eval via next: is that so bad?
by Anonymous Monk on Jun 27, 2019 at 22:27 UTC
    There is now https://metacpan.org/pod/Syntax::Keyword::Try which may allow you to set up your logic in a much cleaner way. In particular, it explicitly supports next/etc as well as return applied to the enclosing stack, unlike eval.