Get out early with Perl statement modifiers
Tags: perl, exceptions, patterns, antipatterns
Published: 2022-01-19 19:45:49

---
title: Get out early with Perl statement modifiers
published: true
date: 2022-01-19 15:00:00 UTC
tags: Perl,exceptions,patterns,antipatterns
canonical_url: https://phoenixtrap.com/2022/01/19/get-out-early-with-perl-statement-modifiers/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rllwes781epnfh6voubu.jpeg
---

When I first started writing Perl in my early 20’s, I tended to follow a lot of the [structured programming](https://en.wikipedia.org/wiki/Structured_programming) conventions I had learned in school through [Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language)), especially the notion that every function has a [single point of exit](https://wiki.c2.com/?SingleFunctionExitPoint). For example:

```perl
sub double_even_number {
    # not using signatures, this is mid-1990's code
    my $number = shift;

    if (not $number % 2) {
        $number *= 2;
    }

    return $number; 
}
```

This could get pretty convoluted, especially if I was doing something like validating multiple arguments. And at the time I didn’t yet grok how to handle [exceptions](https://phoenixtrap.com/2021/02/22/exceptional-perl-failure-is-an-option/) with [`eval`](https://perldoc.perl.org/functions/eval) and `die`, so I’d end up with code like:

```perl
sub print_postal_address {
    # too many arguments, I know
    my ($name, $street1, $street2, $city, $state, $zip) = @_;
    # also this notion of addresses is naive and US-centric

    my $error;

    if (!$name) {
        $error = 'no name';
    }
    else {
        print "$name\n";

        if (!$street1) {
            $error = 'no street';
        }
        else {
            print "$street1\n";

            if ($street2) {
                print "$street2\n";
            }

            if (!$city) {
                $error = 'no city';
            }
            else {
                print "$city, ";

                if (!$state) {
                    $error = 'no state';
                }
                else {
                    print "$state ";

                    if (!$zip) {
                        $error = 'no ZIP code';
                    }
                    else {
                        print "$zip\n";
                    }
                }
            }
        }
    }

    return $error;
}
```

What a mess. Want to count all those braces to make sure they’re balanced? This is sometimes called the [arrow anti-pattern](https://wiki.c2.com/?ArrowAntiPattern), with the arrowhead(s) being the most nested statement. The default [ProhibitDeepNests `perlcritic` policy](https://metacpan.org/pod/Perl::Critic::Policy::ControlStructures::ProhibitDeepNests) is meant to keep you from doing that.

The way out (literally) is [guard clauses](https://wiki.c2.com/?GuardClause): checking early if something is valid and bailing out quickly if not. The above example could be written:

```perl
sub print_postal_address {
    my ($name, $street1, $street2, $city, $state, $zip) = @_;

    if (!$name) {
        return 'no name';
    }
    if (!$street1) {
        return 'no street1';
    }
    if (!$city) {
        return 'no city';
    }
    if (!$state) {
        return 'no state';
    }
    if (!$zip) {
        return 'no zip';
    }

    print join "\n",
      $name,
      $street1,
      $street2 ? $street2 : (),
      "$city, $state $zip\n";

    return;
}
```

With Perl’s [statement modifiers](https://perldoc.perl.org/perlsyn#Statement-Modifiers) (sometimes called postfix controls) we can do even better:

```perl
    ...

    return 'no name'    if !$name;
    return 'no street1' if !$street1;
    return 'no city'    if !$city;
    return 'no state'   if !$state;
    return 'no zip'     if !$zip;

    ...
```

(Why `if` instead of `unless`? Because the latter [can be confusing with double-negatives](https://metacpan.org/pod/Perl::Critic::Policy::ControlStructures::ProhibitUnlessBlocks).)

Guard clauses aren’t limited to the beginnings of functions or even exiting functions entirely. Often you’ll want to skip or even exit early conditions in a loop, like this example that processes files from standard input or the command line:

```perl
while (<>) {
    next if /^SKIP THIS LINE: /;
    last if /^END THINGS HERE$/;

    ...
}
```

Of course, if you are validating function arguments, you should consider using actual [subroutine signatures](https://perldoc.perl.org/perlsub#Signatures) if you have a Perl newer than v5.20 (released in 2014), or one of the [other type validation solutions](https://phoenixtrap.com/2021/01/27/better-perl-with-subroutine-signatures-and-type-validation/) if not. Today I would write that postal function like this, using [Type::Params](https://metacpan.org/pod/Type::Params) for validation and named arguments:

```perl
use feature qw(say state); 
use Types::Standard 'Str';
use Type::Params 'compile_named';

sub print_postal_address {
    state $check = compile_named(
        name    => Str,
        street1 => Str,
        street2 => Str, {optional => 1},
        city    => Str,
        state   => Str,
        zip     => Str,
    );
    my $arg = $check->(@_);

    say join "\n",
      $arg->{name},
      $arg->{street1},
      $arg->{street2} ? $arg->{street2} : (),
      "$arg->{city}, $arg->{state} $arg->{zip}";

    return;
}

print_postal_address(
    name    => 'J. Random Hacker',
    street1 => '123 Any Street',
    city    => 'Somewhereville',
    state   => 'TX',
    zip     => 12345,
);
```

Note that was this part of a larger program, I’d wrap that `print_postal_address` call in a [`try`](https://perldoc.perl.org/perlsyn#Try-Catch-Exception-Handling) block and `catch` exceptions such as those thrown by the code reference `$check` generated by [`compile_named`](https://metacpan.org/pod/Type::Params#compile_named(@spec)). This highlights one concern of guard clauses and other [“return early” patterns](https://medium.com/swlh/return-early-pattern-3d18a41bba8): depending on how much has already occurred in your program, you may have to perform some resource cleanup either in a `catch` block or something like [Syntax::Keyword::Try’s `finally`](https://metacpan.org/pod/Syntax::Keyword::Try#finally) block if you need to tidy up after both success and failure.