Golang's 'defer' in Perl
My day job involves programming in Go. One feature I like about Go is "defer". Any function that's deferred gets ran at the end of the functions scope. As a very simple example, the program below prints
hello
world
instead of the other way around, because "world" is deferred until the main() function exits.
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
I find this cleaner: you can write clean-up closer to where you allocate/acquire resources, instead of remembering to do it at the end of your function. In order to do this in perl, I created a defer() function which, given a subref, returns a wrapper subref blessed into a dummy package. Then, I give that dummy package a destructor (DESTROY) which calls itself. The result is that the subroutine given will be run when the variable it is assigned to goes out of scope:
use v5.10.1;
use strict;
use warnings FATAL => "all";
sub defer{
my ($sub) = @_;
my $pid = $$;
return bless sub {
# I check that I'm running in the process that I called defer() in,
# because I don't want child processes to call $sub->();
if ($$ == $pid) {
$sub->();
}
}, 'my_deferer_dummy_package';
}
sub my_deferer_dummy_package::DESTROY {
my ($self) = @_;
$self->();
}
To give a simple usage example, we create a function which opens a file and defers it's close()ing:
sub upper_caser {
my $file = shift;
open my $fh, '<:crlf', $file;
my $defered = defer sub {
say "defer function called!";
close $fh;
};
while (defined(my $line = <$fh>)){
chomp $line;
say uc $line;
}
}
there's a file called "test.txt" containing the lines:
just
another
perl
hacker
Now, calling the function,
upper_caser "test.txt";
it yields:
# JUST
# ANOTHER
# PERL
# HACKER
# defer function called!
I also found this very useful in functional programming, when I want to return an iterator subroutine, but also clean-up after it when it is no longer needed:
sub make_upper_caser_iterator {
my $file = shift;
open my $fh, '<:crlf', $file;
my $defered = defer sub {
say "defer function in closure called!";
close $fh;
};
return sub{
# make sure that the closure captures $defered
# and keeps it alive as long as this closure is.
my $defered = $defered;
if (defined(my $line = <$fh>)){
chomp $line;
return $line;
}
return;
};
}
my $iter = make_upper_caser_iterator "test.txt";
while (defined(my $line = $iter->())){
say uc $line;
}
As expected, the above use of the iterator yields:
JUST
ANOTHER
PERL
HACKER
defer function in closure called!