Version 5.36 of perl was recently released. Amid the new features
it contains, there is a very interesting, and maybe lesser-know one: the new defer
keyword.
What does it do? It simply defers the execution of some code to the end of the enclosing block. It’s a very useful construct, because (in my opinion) it allows to make code much cleaner.
I put down a simple example which renders a checkout page for an fictional e-commerce site. There are some conditions which bring to different result, with instruction
The first version uses a classic if/else approach. Since there are functions to be called between evaluation of conditions, the if/elsif approach on a single level does not fit, so a nesting of conditional statements is needed.
sub checkout() {
my $tplargs;
if ( !is_user_logged() ) {
$tplargs->{error} = 'not_logged';
} else {
my $user = get_user();
my $cart = get_cart_contents();
if ( !@$cart ) {
$tplargs->{error} = 'empty_cart';
} else {
my $shrate = calc_shipping_rate($user, $cart);
if ( !defined $shrate ) {
$tplargs->{error} = 'cant_ship_there';
} else {
my $res = finalize_order($user, $cart, $shrate);
if ( $res->{status} eq 'ko' ) {
$tplargs->{error} = 'processing_error';
} else {
$tplargs->{success} = 1;
}
}
}
}
render_template('checkout.html', $tplargs);
}
Not he best, uh?
The second attempt moves everything to a single level, using return statements. Without nesting, code is more
straightforward but longer, especially the render_template('checkout.html', $tplargs);
instruction.
sub checkout() {
my $tplargs;
if ( !is_user_logged() ) {
$tplargs->{error} = 'not_logged';
render_template('checkout.html', $tplargs);
return;
}
my $user = get_user();
my $cart = get_cart_contents();
if ( !@$cart ) {
$tplargs->{error} = 'empty_cart';
render_template('checkout.html', $tplargs);
return;
}
my $shrate = calc_shipping_rate($user, $cart);
if ( !defined $shrate ) {
$tplargs->{error} = 'cant_ship_there';
render_template('checkout.html', $tplargs);
return;
}
my $res = finalize_order($user, $cart, $shrate);
if ( $res->{status} eq 'ko' ) {
$tplargs->{error} = 'processing_error';
render_template('checkout.html', $tplargs);
return;
}
$tplargs->{success} = 1;
render_template('checkout.html', $tplargs);
}
Enter defer
, which allows to define at the top (or somewhere else) in the sub something which must happen at
the end of the execution scope.
sub checkout() {
my ($error, $tplargs);
defer {
$tplargs->{error} = $error;
render_template('checkout.html', $tplargs);
}
if ( !is_user_logged() ) {
$error = 'not_logged';
return;
}
my $user = get_user();
my $cart = get_cart_contents();
if ( !@$cart ) {
$error = 'empty_cart';
return;
}
my $shrate = calc_shipping_rate($user, $cart);
if ( !defined $shrate ) {
$error = 'cant_ship_there';
return;
}
my $res = finalize_order($user, $cart, $shrate);
if ( $res->{status} eq 'ko' ) {
$error = 'processing_error';
return;
}
$tplargs->{success} = 1;
}
Cleaner and elegant, isn’t it? It is also informative, as reading the beginning of the sub, it’s immediately obvious what happens at the end.
I think defer
will become a very appreciated addition to the Perl language.