Lexical closures with END blocks warn, but Just Work

I was just impressed by the awesomeness of Perl. More testing revealed to me that "use 5.018" does not invoke "use warnings", which dampened my enthusiasm a little. But Perl is still pretty awesome. Here's the situation.

I write lots of Prima GUI applications. In this one (a presentation if you can believe that), I wanted a timer so I could see how long my talk was going. I had just run through part of my lecture and meant to check my time before closing (the clock is discretely placed in a separate window), but forgot to check before quitting the talk. Frustrated, I decided that I wanted my program to print the talk duration when it was finished.

My normal approach would have been to declare a file-scope lexical that gets initialized at startup, and refer to that in my END block. But instead, I decided to embed the END block within the initialization function itself.

Stripping it down to the bare basics, the code looks something like this:

sub import {
    # ----%<----
    my $start_time = localtime;
    Prima::Timer->new(
        timeout => 1000,  # milliseconds
        onTick => sub {
            my $seconds_so_far = localtime() - $start_time;
            # update clock with latest time
        }
    )->start;
    END {
        my $seconds_at_end = localtime() = $start_time;
        # print out final duration
    }
    # ---->%----
}

Astoundingly, it Just Worked. It even read my mind and didn't give me a warning, which I half expected.

I then had a weird thought. "If I called this multiple times," I wondered, "would it create multiple END blocks?" In my circumstance this would never arise because this module is only ever imported once per lecture. But I still thought it interesting.

Well, this code answers that question:

use strict;
use warnings;

sub print_it {
my $value = shift;
print "During execution, got [$value]\n";
END {
print "At end, had value of [$value]\n";
}
}

print_it 'hello';
print_it 5;
print_it 1.34;

The output was this:


Variable "$value" will not stay shared at test.pl line 8.
During execution, got [hello]
During execution, got [5]
During execution, got [1.34]
At end, had value of [hello]

Oh! So it turns out that the first value of the variable is closed over in exactly the same ways as if I had a "sub" declaration within another sub, and the inner sub closed over a lexical variable from the outer sub. And it issued a warning, which I did not expect.

So the result is: an END block is only created once, and it closes over the values set in the first pass through the code. (Thus, if you close over a variable in code that is never called, the value will be undef. Good to know.)

The reason this had not occurred to me as a possibility was that I could have sworn that "use 5.018" invoked "use warnings". I thought I had gone so far as to test it.

Anyway, if you want to do something like this, and you want to close over the first incantation of the variable, you can always add this to your END block to suppress that warning:


no warnings 'closure';

Now you know. Happy Perling!

3 Comments

You could add the values to a state variable.

sub print_it {
    my $value = shift;
    print "During execution, got [$value]\n";
    state $old = [];
    # Uses push instead of unshift
    # because push is probably faster on long arrays.
    push @$old, $value;
    END{
        # uses reverse to emulate normal END behaviour.
        print "At end, had value of [$_]\n" for reverse @$old;
    }
}

or you could generate the END block each time the sub is called.

sub print_it {
    my $value = shift;
    print "During execution, got [$value]\n";
    eval qq{END{
        print "At end, had value of [$value]\n";
    }};
}

This is only a trivium, but caught my eye, and in the hope that it may avoid confusion, here's a quick note that the END block closes over the instance of the lexical variable that's around on the first call to print_it, rather than its actual value. As a quick example:

sub print_it {
  my $value = shift;
  print "During execution, got [$value]\n";
  return sub { ${\$value} = shift; };
  END {
    print "At end, had value of [$value]\n";
  }
}
my(@mutators) = map { print_it($_) } qw/ hello 5 1.34 /; 
$mutators[0]->('goodbye') if $ARGV[0] eq 'end';
$mutators[1]->('not so')  if $ARGV[0] eq 'other';
__END__

$ perl ./print_me other
During execution, got [hello]
During execution, got [5]
During execution, got [1.34]
At end, had value of [hello]

$ perl ./print_me end
During execution, got [hello]
During execution, got [5]
During execution, got [1.34]
At end, had value of [goodbye]

Thanks for the interesting observation!

Leave a comment

About David Mertens

user-pic This is my blog about numerical computing with Perl.