6.13. Complex Mappings
Use a subroutine call to factor out complex list transformations.
When a map, grep, or first is applied to a list, the block performing the transformation or conditional test can sometimes become quite complex. For example:
use List::Util qw( max );
Readonly my $JITTER_FACTOR => 0.01; # Jitter by a maximum of 1%
my @jittered_points
= map { my $x = $_>{x};
my $y = $_>{y};
my $max_jitter = max($x, $y) / $JITTER_FACTOR;
{ x => $x + gaussian_rand({mean=>0, dev=>0.25, scale=>$max_jitter}),
y => $y + gaussian_rand({mean=>0, dev=>0.25, scale=>$max_jitter}),
}
} @points;
This large block is very hard to read, especially since the final anonymous hash constructor looks more like a nested block. So the temptation is to use a for instead:
my @jittered_points;
for my $point (@points) {
my $x = $point>{x};
my $y = $point>{y};
my $max_jitter = max($x, $y) / $JITTER_FACTOR;
my $jittered_point = {
x => $x + gaussian_rand({ mean=>0, dev=>0.25, scale=>$max_jitter }),
y => $y + gaussian_rand({ mean=>0, dev=>0.25, scale=>$max_jitter }),
};
push @jittered_points, $jittered_point;
}
That certainly does help the overall readability, but it's still far from optimal. A better solution is to factor out the complex calculation into a separate subroutine, then call that subroutine within a now much simpler and more readable map expression:
my @jittered_points = map { jitter($_) } @points;
# and elsewhere...
# Add a random Gaussian perturbation to a point...
sub jitter {
my ($point) = @_;
my $x = $point>{x};
my $y = $point>{y};
my $max_jitter = max($x, $y) / $JITTER_FACTOR;
return {
x => $x + gaussian_rand({ mean=>0, dev=>0.25, scale=>$max_jitter }),
y => $y + gaussian_rand({ mean=>0, dev=>0.25, scale=>$max_jitter }),
};
}
