develooper Front page | perl.perl5.porters | Postings from July 2020

Latest thoughts on LEAVE - it is now CLEANUP

Thread Next
From:
Paul "LeoNerd" Evans
Date:
July 15, 2020 22:02
Subject:
Latest thoughts on LEAVE - it is now CLEANUP
Message ID:
20200715230207.353b38cc@shy.leonerd.org.uk
A quick update on my experimentations.

I have decided that the keyword ought to be `CLEANUP`, rather than
`LEAVE`. The reason is that the semantics now don't match those of
Raku's `LEAVE`, so it deserves a different name.

The main change is that `CLEANUP` is a line-based statement which only
enqueues the block for later execution if the line itself is reached.
Thus, given

  {
    CLEANUP { say "This runs" }
    last;
    CLEANUP { say "This doesn't" }
  }

the first `say` statement runs but the second does not.

For those who want to follow along at home, you can see my current
branch at

  https://github.com/Perl/perl5/tree/leonerd/cleanup_block

and in particular, the unit test file for this new syntax at

  https://github.com/Perl/perl5/blob/leonerd/cleanup_block/t/op/CLEANUP.t

Thanks should go to Branislav ZahradnĂ­k (@happy-barney) for providing
many of these exciting test cases.


-----

Further details for interested folks:


This change comes because it turns out to solve a number of awkward
cases. The most interesting user-facing one was that

  my $thing = SomeClass->new(%args);
  LEAVE { $thing->dispose }

would complain. If the SomeClass constructor throws an exception,
meaning the object is never created, the LEAVE block still runs anyway
and tries to invoke a method on undef. By giving CLEANUP the semantics
that it is only "armed" if that line of code is run, this neatly fixes
it.

  my $thing = SomeClass->new(%args);
  CLEANUP { $thing->dispose }

now only runs ->dispose if the constructor hadn't thrown.

This also has the nice bonus that CLEANUP can be used instead of
END in a toplevel script if it is more appropriate. Often such END
blocks need to be guarded. An example from my own code:

  $chip->protocol->power(1)->get;
  END { $chip and $chip->protocol->power(0)->get }

  https://metacpan.org/source/PEVANS/Device-Chip-BNO055-0.01/examples/bno055.pl#L31

I have to guard the code inside the END block with `$chip and ...` to
protect against being run if $chip isn't assigned. It could be written
neater using CLEANUP:

  $chip->protocol->power(1)->get;
  CLEANUP { $chip->protocol->power(0)->get }

While initially I wanted the semantics to be similar to END to avoid
user confusion, it did mean that a LEAVE block at toplevel of main
scope is identical to END. Now with CLEANUP they are usefully
different, and able to do different things. I think this benefit
outweighs the fact that it is different to END.

Additionally, this form is easier and simpler to implement than the
previous LEAVE attempt. It also makes it possible for cases like:

  {
    my $var = "value"
    CLEANUP { say "Var is $var" }
  }

to actually work on an inner details technical level. The previous
version involving LEAVE would see `undef` as the value of `$var`
because it took place after the lexicals are tidied at end of block
scope. The newer CLEANUP block happens earlier, before lexicals are
tidied up, so it can see their expected values.

-- 
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk      |  https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/  |  https://www.tindie.com/stores/leonerd/

Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About