Mailing List Archive

RFC 0004 - defer {} syntax
Here's an RFC formalization of my currently-open github feature issue:

https://github.com/Perl/perl5/issues/17949

which was itself an issue tracking the previous mailing list
conversation:

https://www.nntp.perl.org/group/perl.perl5.porters/2020/06/msg257611.html

If we accept this one into the RFC process, I'll likely close the
github issue and point at the RFC instead.

---

# defer {} syntax

## Preämble

Author: Paul Evans <PEVANS>
Sponsor:
ID: 0004
Status: Draft

## Abstract

Add a new control-flow syntax written as `defer { BLOCK }` which
enqueues a block of code for later execution, when control leaves its
surrounding scope for whatever reason.

## Motivation

Sometimes a piece of code performs some sort of "setup" operation, that
requires a corresponding "teardown" to happen at the end of its task.
Control flow such as exception throwing, or loop controls, means that
it cannot always be written in a simple style such as

{
setup();
operation();
teardown();
}

as in this case, if the `operation` function throws an exception the
teardown does not take place. Traditional solutions to this often rely
on allocating a lexical variable and storing an instance of some object
type with a `DESTROY` method, so this runs the required code as a result
of object destruction caused by variable cleanup. There are no in-core
modules to automate this process, but the CPAN module `Scope::Guard` is
among the more popular choices.

It would be nice to offer a native core syntax for this common
behaviour. A simple `defer { BLOCK }` syntax removes from the user any
requirement to think about storing the guard object in a lexical
variable, or worry about making sure it really does get released at the
right time.

As well as being more convenient for users, it would help decouple Perl
core's object behaviours, like `DESTROY`, from this task. If less code
relies on DESTROY, it may one day be possible to change the way that
part of the code works, without worrying so much about code that relies
on it breaking.

This syntax has already been implemented as a CPAN module,
[`Syntax::Keyword::Defer`](https://metacpan.org/pod/Syntax::Keyword::Defer).
This RFC formalizes an attempt implement the same in core.

## Rationale

((TODO - I admit I'm not very clear on how to split my wording between
the Motivation section, and this one))

The name "defer" comes from a collection of other languages.
Near-identical syntax is provided by Swift, Zig, Jai, Nim and Odin. Go
does define a `defer` keyword that operates on a single statement,
though its version defers until the end of the containing function, not
just a single lexical block. I did consider this difference, but ended
up deciding that a purely-lexical scoped nature is cleaner and more
"Perlish", overriding the concerns that it differs from Go.

## Specification

A new lexically-scoped feature bit, requested as

use feature 'defer';

enables new syntax, spelled as

defer { BLOCK }

This syntax stands alone as a full statement; much as e.g. a `while`
loop does. The deferred block may contain one or multiple statements.

When the `defer` statement is encountered during regular code
execution, nothing immediately happens. The contents of the block are
stored by the perl interpreter, enqueued to be invoked at some later
time, when control exits the block this `defer` statement is contained
within.

If multiple `defer` statements appear within the same block, the are
eventually executed in LIFO order; that is, the most recently
encountered is the first one to run:

{
setup1();
defer { say "This runs second"; teardown1(); }

setup2();
defer { say "This runs first"; teardown2(); }
}

`defer` statements are only "activated" if the flow of control actually
encounters the line they appear on (as compared to `END` phaser blocks
which are activated the moment they have been parsed by the compiler).
If the `defer` statement is never reached, then its deferred code will
not be invoked:

while(1) {
defer { say "This will happen"; }
last;
defer { say "This will *NOT* happen"; }
}

((TODO: It is currently not explicitly documented, but naturally falls
out of the current implementation of both the CPAN and the in-core
versions, that the same LIFO stack that implements `defer` also
implements other things such as `local` modifications; for example:

our $var = 1;
{
defer { say "This prints 1: $var" }

local $var = 2;
defer { say "This prints 2: $var" }
}

I have no strong thoughts yet on whether to specify and document -
and thus guarantee - this coïncidence, or leave it unsaid.))

## Backwards Compatibility

The new syntax `defer { BLOCK }` is guarded by a lexical feature guard.
A static analyser that is not aware of that feature guard would get
confused into thinking this is indirect call syntax; though this is no
worse than basically any other feature-guarded keyword that controls a
block (e.g. `try/catch`).

For easy compatibility on older Perl versions, the CPAN implementation
already mentioned above can be used. If this RFC succeeds at adding it
as core syntax, a `Feature::Compat::Defer` module can be created for it
in the same style as
[`Feature::Compat::Try`](https://metacpan.org/pod/Feature::Compat::Try).

## Security Implications

None foreseen.

## Examples

A couple of small code examples are quoted above. Further, from the
docs of `Syntax::Keyword::Defer`:

use feature 'defer';

{
my $dbh = DBI->connect( ... ) or die "Cannot connect";
defer { $dbh->disconnect; }

my $sth = $dbh->prepare( ... ) or die "Cannot prepare";
defer { $sth->finish; }

...
}

## Prototype Implementation

CPAN module `Syntax::Keyword::Defer` as already mentioned.

In addition, I have a mostly-complete branch of bleadperl (somewhat
behind since I haven't updated it for the 5.34 release yet) at

https://github.com/leonerd/perl5/tree/defer

I'm not happy with the way I implemented it yet (don't look at how I
abused an SVOP to store the deferred optree) - it needs much tidying up
and fixing for the various things I learned while writing the CPAN
module version.

## Future Scope

If this RFC becomes implemented, it naturally follows to enquire
whether the same mechanism that powers it could be used to add a
`finally` clause to the `try/catch` syntax added in Perl 5.34. This
remains an open question: while it doesn't any new ability, is the
added expressive power and familiarity some users will have enough to
justify there now being two ways to write the same thing?

## Rejected Ideas

On the subject of naming, this was originally called `LEAVE {}`, which
was rejected because its semantics don't match the Raku language
feature of the same name. It was then renamed to `FINALLY {}` where it
was implemented on CPAN. This has been rejected too on grounds that
it's too similar to the proposed `try/catch/finally`, and it shouldn't
be a SHOUTY PHASER BLOCK. All the SHOUTY PHASER BLOCKS are declarations,
activated by their mere presence at compiletime, whereas `defer {}` is
a statement which only takes effect if dynamic execution actually
encounters it. The p5p mailing list and the CPAN module's RT queue both
contain various other rejected naming ideas, such as UNWIND, UNSTACK,
CLEANUP, to name three.

Another rejected idea is that of conditional enqueue:

defer if (EXPR) { BLOCK }

as this adds quite a bit of complication to the grammar, for little
benefit. As currently the grammar requires a brace-delimited block to
immediately follow the `defer` keyword, it is possible that other ideas
- such as this one - could be considered at a later date however.

## Open Issues

Design-wise I don't feel there are any remaining unresolved questions.

Implementation-wise the code still requires some work to finish it off;
it is not yet in a merge-ready state.

## Copyright

Copyright (C) 2021, Paul Evans.
This document and code and documentation within it may be used,
redistributed and/or modified under the same terms as Perl itself.

--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: RFC 0004 - defer {} syntax [ In reply to ]
"Paul \"LeoNerd\" Evans" <leonerd@leonerd.org.uk> wrote:
:Here's an RFC formalization of my currently-open github feature issue:
:
: https://github.com/Perl/perl5/issues/17949
:
:which was itself an issue tracking the previous mailing list
:conversation:
:
: https://www.nntp.perl.org/group/perl.perl5.porters/2020/06/msg257611.html
[...]

:As well as being more convenient for users, it would help decouple Perl
:core's object behaviours, like `DESTROY`, from this task. If less code
:relies on DESTROY, it may one day be possible to change the way that
:part of the code works, without worrying so much about code that relies
:on it breaking.

At least the second sentence here feels like a red herring. I'd be very
happy to see defer in the language, but I cannot envisage its existence
significantly reducing the need to retain DESTROY behaviour within any
relevant timescale.

:Another rejected idea is that of conditional enqueue:
:
: defer if (EXPR) { BLOCK }

Given the (snipped) reference to symmetry with the behaviour of local,
I would be hopeful that you could get the same effect with:
defer BLOCK if EXPR;
just as we can get conditional localization (something of which I make
heavy use in recursive code), eg:
local @hash{@keys} = @values if $localize;

Hugo
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Wed, Jun 16, 2021 at 7:21 PM <hv@crypt.org> wrote:

> :Another rejected idea is that of conditional enqueue:
> :
> : defer if (EXPR) { BLOCK }
>
> Given the (snipped) reference to symmetry with the behaviour of local,
> I would be hopeful that you could get the same effect with:
> defer BLOCK if EXPR;
> just as we can get conditional localization (something of which I make
> heavy use in recursive code), eg:
> local @hash{@keys} = @values if $localize;
>

I had a similar thought - but this would necessitate making `defer BLOCK`
an expression rather than a complete statement, so that it could be used
with statement modifiers similarly to `do BLOCK`; this means that bare
`defer BLOCK` will need a semicolon. It seems useful for if and unless
conditions, but haven't really thought it through.

-Dan
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Wed, 16 Jun 2021 23:47:27 +0100
hv@crypt.org wrote:

> "Paul \"LeoNerd\" Evans" <leonerd@leonerd.org.uk> wrote:
> :Here's an RFC formalization of my currently-open github feature
> issue: :
> : https://github.com/Perl/perl5/issues/17949
> :
> :which was itself an issue tracking the previous mailing list
> :conversation:
> :
> :
> https://www.nntp.perl.org/group/perl.perl5.porters/2020/06/msg257611.html
> [...]
>
> :As well as being more convenient for users, it would help decouple
> Perl :core's object behaviours, like `DESTROY`, from this task. If
> less code :relies on DESTROY, it may one day be possible to change
> the way that :part of the code works, without worrying so much about
> code that relies :on it breaking.
>
> At least the second sentence here feels like a red herring. I'd be
> very happy to see defer in the language, but I cannot envisage its
> existence significantly reducing the need to retain DESTROY behaviour
> within any relevant timescale.

Fair - I guess I could just delete that bit. It's a _far_ off potential
idea anyway.

> :Another rejected idea is that of conditional enqueue:
> :
> : defer if (EXPR) { BLOCK }
>
> Given the (snipped) reference to symmetry with the behaviour of local,
> I would be hopeful that you could get the same effect with:
> defer BLOCK if EXPR;
> just as we can get conditional localization (something of which I make
> heavy use in recursive code), eg:
> local @hash{@keys} = @values if $localize;

defer { BLOCK }
^-- the parser has now stopped looking for a complete
statement


defer { BLOCK } if EXPR;
^-- oops - parser wanted to find a `(` because it was
expecting if(EXPR) { BLOCK }


--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: RFC 0004 - defer {} syntax [ In reply to ]
2021-6-17 7:16 Paul "LeoNerd" Evans <leonerd@leonerd.org.uk> wrote:

> Here's an RFC formalization of my currently-open github feature issue:
>
> https://github.com/Perl/perl5/issues/17949
>
>
Personally, I think defer block syntax is difficult to implement.

Especially the relation of

1. GC
2. Destructor

The relation of GG and block of Current Perl is simple. an object is
released at the end of the block if the object reference count is 0.

defer syntax maybe add complex things of this relation.

This feature seems to be planned for the finally block of try catch syntax.

Are there other strong benefits?
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, 17 Jun 2021 at 07:15, Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>
wrote:

> The name "defer" comes from a collection of other languages.
> Near-identical syntax is provided by Swift, Zig, Jai, Nim and Odin. Go
> does define a `defer` keyword that operates on a single statement,
> though its version defers until the end of the containing function, not
> just a single lexical block.


"defer" in Go is used to catch ("recover") exceptions ("panic") which
covers "very bad" things like memory errors (segmentation fault, etc.) so I
think it has to be at the "end of the function" since it is basically being
called as you go back down the stack of functions when recovering from a
"disaster" in a called routine. For example if you write an http server in
Go and the function you pass to make the web page crashes, the http server
doesn't stop functioning but just prints a message and continues.

That requirement doesn't seem to apply here.


> I did consider this difference, but ended
> up deciding that a purely-lexical scoped nature is cleaner and more
> "Perlish", overriding the concerns that it differs from Go.
>

I can imagine cases where you might want to clean up after each go around
of a loop. But I find it quite hard to imagine how it would be useful to
have a function which runs at the end of an "if" statement block regardless
of what else happens.

Would it be useful to make the "defer" statement within an "if" block refer
to the enclosing function/loop/other block rather than the "if" block, in
the same way that a "next" statement within the if block refers to the
enclosing loop?
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, 17 Jun 2021 at 08:37, Ben Bullock <benkasminbullock@gmail.com>
wrote:

> I can imagine cases where you might want to clean up after each go around
> of a loop. But I find it quite hard to imagine how it would be useful to
> have a function which runs at the end of an "if" statement block regardless
> of what else happens.
>

As a generic example:

if($resource->acquire) {
defer { $resource->release; }
... return, exception, next, or similar action here ...
}.
# This line is never reached, so the ->release doesn't happen
# $resource->release;


Would it be useful to make the "defer" statement within an "if" block refer
> to the enclosing function/loop/other block rather than the "if" block, in
> the same way that a "next" statement within the if block refers to the
> enclosing loop?
>

This would introduce more complexity to the mental model - it's scoped to
the block unless it's an `if/else/elsif/unless` block, but unlike next/last
you can't override the scope with a label - and would take it further away
from the behaviour currently provided by Scope::Guard or
B::Hooks::EndOfScope.

Do you have any examples in mind where this would be a better fit than the
scoped-to-block implementation currently described, though?
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, 17 Jun 2021 at 09:50, Tom Molesworth <tom@binary.com> wrote:

> On Thu, 17 Jun 2021 at 08:37, Ben Bullock <benkasminbullock@gmail.com>
> wrote:
>
>> I can imagine cases where you might want to clean up after each go around
>> of a loop. But I find it quite hard to imagine how it would be useful to
>> have a function which runs at the end of an "if" statement block regardless
>> of what else happens.
>>
>
> As a generic example:
>
> if($resource->acquire) {
> defer { $resource->release; }
> ... return, exception, next, or similar action here ...
> }.
> # This line is never reached, so the ->release doesn't happen
> # $resource->release;
>

To clarify, in terms of your example, my query is about this:

sub superduper
{
if ($resource->acquire) {
defer { $resource->release }
if ($resource->failure) {
# Case failure: Either way it runs here
return undef;
}
if (0 == -1) {
# Case exception: Either way it runs here
die "Oh no I have failed";
}
if ($resource->blocks) {
# Case next: Either way it runs here.
next;
}
# Case if block finishes: At the moment it runs here <-----------
why does it run here?
}
# Case enclosing loop finishes: <------------- Why not run here instead?
return 1;
}

> Do you have any examples in mind where this would be a better fit than
the scoped-to-block implementation currently described, though?

The "defer" is called within the "if" statement then runs at the end of the
if statement, I'm wondering why it can't run at the end of the loop
instead. Then we can do things like this:

while (1) {
if ($resource->too_busy) {
defer {
$resource->close_some_connections;
}
# Don't want to clean up yet.
}
$resource->do_something;
$resource->do_another_thing;
# Clean up here with the defer statements.
}

The reason I'm saying it is because I can't see a big advantage from
running at the end of an if block, but I can see an advantage from being
able to run defers conditionally.
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, 17 Jun 2021 10:44:37 +0900, Ben Bullock
<benkasminbullock@gmail.com> wrote:

> On Thu, 17 Jun 2021 at 09:50, Tom Molesworth <tom@binary.com> wrote:
>
> > On Thu, 17 Jun 2021 at 08:37, Ben Bullock
> > <benkasminbullock@gmail.com> wrote:
> >
> >> I can imagine cases where you might want to clean up after each go
> >> around of a loop. But I find it quite hard to imagine how it would
> >> be useful to have a function which runs at the end of an "if"
> >> statement block regardless of what else happens.
> >>
> >
> > As a generic example:
> >
> > if($resource->acquire) {
> > defer { $resource->release; }
> > ... return, exception, next, or similar action here ...
> > }.
> > # This line is never reached, so the ->release doesn't happen
> > # $resource->release;
>
> To clarify, in terms of your example, my query is about this:
>
> sub superduper {
> if ($resource->acquire) {
> defer { $resource->release }
> if ($resource->failure) {
> # Case failure: Either way it runs here
> return undef;
> }
> if (0 == -1) {
> # Case exception: Either way it runs here
> die "Oh no I have failed";
> }
> if ($resource->blocks) {
> # Case next: Either way it runs here.
> next;

Exiting subroutine via next at -e line N
Did you meant return here?

> }
> # Case if block finishes: At the moment it runs here <----------- # why does it run here?
> }
> # Case enclosing loop finishes: <------------- Why not run here instead?
> return 1;
> }
>
> > Do you have any examples in mind where this would be a better fit
> > than the scoped-to-block implementation currently described,
> > though?
>
> The "defer" is called within the "if" statement then runs at the end
> of the if statement, I'm wondering why it can't run at the end of the
> loop instead. Then we can do things like this:
>
> while (1) {
> if ($resource->too_busy) {
> defer {
> $resource->close_some_connections;
> }
> # Don't want to clean up yet.
> }
> $resource->do_something;
> $resource->do_another_thing;
> # Clean up here with the defer statements.
> }
>
> The reason I'm saying it is because I can't see a big advantage from
> running at the end of an if block, but I can see an advantage from
> being able to run defers conditionally.

--
H.Merijn Brand https://tux.nl Perl Monger http://amsterdam.pm.org/
using perl5.00307 .. 5.33 porting perl5 on HP-UX, AIX, and Linux
https://tux.nl/email.html http://qa.perl.org https://www.test-smoke.org
Re: RFC 0004 - defer {} syntax [ In reply to ]
From the keyboard of Ben Bullock [17.06.21,10:44]:

[...]
> To clarify, in terms of your example, my query is about this:
>
> sub superduper 
> {
>    if ($resource->acquire) {
>        defer { $resource->release }
>        if ($resource->failure) {
>              # Case failure: Either way it runs here
>              return undef;
>         }
>         if (0 == -1) {
>              # Case exception: Either way it runs here
>              die "Oh no I have failed";
>          }
>          if ($resource->blocks) {
>               # Case next: Either way it runs here.
>               next;
>           }
>        # Case if block finishes: At the moment it runs here <----------- why does it run
> here?
>    }
>    # Case enclosing loop finishes: <------------- Why not run here instead?
>    return 1;
> }
>
> > Do you have any examples in mind where this would be a better fit than the
> scoped-to-block implementation currently described, though?
>
> The "defer" is called within the "if" statement then runs at the end of the if statement,
> I'm wondering why it can't run at the end of the loop instead. Then we can do things like
> this:
>
> while (1) {
>    if ($resource->too_busy) {
>         defer {
>              $resource->close_some_connections;
>         }
>         # Don't want to clean up yet.
>     }
>     $resource->do_something;
>     $resource->do_another_thing;
>     # Clean up here with the defer statements.
> }
>
> The reason I'm saying it is because I can't see a big advantage from running at the end of
> an if block, but I can see an advantage from being able to run defers conditionally.
>
>

Maybe "defer" should follow the semantics of "next" and "last", which would
allow to say "defer BLOCK" and "defer LABEL BLOCK", with a special label or
label modifier named ":outer" (or ":after" ?).

sub foo {
my $lim = shift;
my (@l, $c);
QWURX: for my $x (1..5) {
defer { say "candidate $x" };
return if $x > $lim;
my $y = $x << 1;
if ($x % 2) {
defer :outer { say "y = $y" }; #2, same as defer QWURX
$c++;
defer :outer QWURX { say "$c. even number" }; #3 also: defer QWURX :outer { }
say "x % 2";
}
say "x = $x";
# defer block #2 runs here
# defer block #1 runs here
}
# defer block(s) #3 run(s) here
}

So, a deferred block can be hooked to a labelled block (or a bare label) at its
end or outside of it. Maybe the modfier ":return" would be useful, to hook the
deferred blocks to the end of or return from a subroutine.

0--gg-

--
_($_=" "x(1<<5)."?\n".q·/)Oo. G°\ /
/\_¯/(q /
---------------------------- \__(m.====·.(_("always off the crowd"))."·
");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: RFC 0004 - defer {} syntax [ In reply to ]
There's already a set of modules on CPAN
(https://metacpan.org/pod/Guard,
https://metacpan.org/pod/Scope::Guard, others) doing the same thing
using a scope_guard/guard verb. So, I have a number of questions:

- why invent a new verb for already existing semantic?
- what do all current CPAN implementations lack, thus requiring this feature?
- why has this to be a core feature, compared to yet another CPAN module?
- why any existing module can't be bundled with Perl, achieving this
functionality?

Best regards,
Sergey Aleynikov

??, 17 ???. 2021 ?. ? 01:15, Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>:
>
> Here's an RFC formalization of my currently-open github feature issue:
>
> https://github.com/Perl/perl5/issues/17949
>
> which was itself an issue tracking the previous mailing list
> conversation:
>
> https://www.nntp.perl.org/group/perl.perl5.porters/2020/06/msg257611.html
>
> If we accept this one into the RFC process, I'll likely close the
> github issue and point at the RFC instead.
>
> ---
>
> # defer {} syntax
>
> ## Preämble
>
> Author: Paul Evans <PEVANS>
> Sponsor:
> ID: 0004
> Status: Draft
>
> ## Abstract
>
> Add a new control-flow syntax written as `defer { BLOCK }` which
> enqueues a block of code for later execution, when control leaves its
> surrounding scope for whatever reason.
>
> ## Motivation
>
> Sometimes a piece of code performs some sort of "setup" operation, that
> requires a corresponding "teardown" to happen at the end of its task.
> Control flow such as exception throwing, or loop controls, means that
> it cannot always be written in a simple style such as
>
> {
> setup();
> operation();
> teardown();
> }
>
> as in this case, if the `operation` function throws an exception the
> teardown does not take place. Traditional solutions to this often rely
> on allocating a lexical variable and storing an instance of some object
> type with a `DESTROY` method, so this runs the required code as a result
> of object destruction caused by variable cleanup. There are no in-core
> modules to automate this process, but the CPAN module `Scope::Guard` is
> among the more popular choices.
>
> It would be nice to offer a native core syntax for this common
> behaviour. A simple `defer { BLOCK }` syntax removes from the user any
> requirement to think about storing the guard object in a lexical
> variable, or worry about making sure it really does get released at the
> right time.
>
> As well as being more convenient for users, it would help decouple Perl
> core's object behaviours, like `DESTROY`, from this task. If less code
> relies on DESTROY, it may one day be possible to change the way that
> part of the code works, without worrying so much about code that relies
> on it breaking.
>
> This syntax has already been implemented as a CPAN module,
> [`Syntax::Keyword::Defer`](https://metacpan.org/pod/Syntax::Keyword::Defer).
> This RFC formalizes an attempt implement the same in core.
>
> ## Rationale
>
> ((TODO - I admit I'm not very clear on how to split my wording between
> the Motivation section, and this one))
>
> The name "defer" comes from a collection of other languages.
> Near-identical syntax is provided by Swift, Zig, Jai, Nim and Odin. Go
> does define a `defer` keyword that operates on a single statement,
> though its version defers until the end of the containing function, not
> just a single lexical block. I did consider this difference, but ended
> up deciding that a purely-lexical scoped nature is cleaner and more
> "Perlish", overriding the concerns that it differs from Go.
>
> ## Specification
>
> A new lexically-scoped feature bit, requested as
>
> use feature 'defer';
>
> enables new syntax, spelled as
>
> defer { BLOCK }
>
> This syntax stands alone as a full statement; much as e.g. a `while`
> loop does. The deferred block may contain one or multiple statements.
>
> When the `defer` statement is encountered during regular code
> execution, nothing immediately happens. The contents of the block are
> stored by the perl interpreter, enqueued to be invoked at some later
> time, when control exits the block this `defer` statement is contained
> within.
>
> If multiple `defer` statements appear within the same block, the are
> eventually executed in LIFO order; that is, the most recently
> encountered is the first one to run:
>
> {
> setup1();
> defer { say "This runs second"; teardown1(); }
>
> setup2();
> defer { say "This runs first"; teardown2(); }
> }
>
> `defer` statements are only "activated" if the flow of control actually
> encounters the line they appear on (as compared to `END` phaser blocks
> which are activated the moment they have been parsed by the compiler).
> If the `defer` statement is never reached, then its deferred code will
> not be invoked:
>
> while(1) {
> defer { say "This will happen"; }
> last;
> defer { say "This will *NOT* happen"; }
> }
>
> ((TODO: It is currently not explicitly documented, but naturally falls
> out of the current implementation of both the CPAN and the in-core
> versions, that the same LIFO stack that implements `defer` also
> implements other things such as `local` modifications; for example:
>
> our $var = 1;
> {
> defer { say "This prints 1: $var" }
>
> local $var = 2;
> defer { say "This prints 2: $var" }
> }
>
> I have no strong thoughts yet on whether to specify and document -
> and thus guarantee - this coïncidence, or leave it unsaid.))
>
> ## Backwards Compatibility
>
> The new syntax `defer { BLOCK }` is guarded by a lexical feature guard.
> A static analyser that is not aware of that feature guard would get
> confused into thinking this is indirect call syntax; though this is no
> worse than basically any other feature-guarded keyword that controls a
> block (e.g. `try/catch`).
>
> For easy compatibility on older Perl versions, the CPAN implementation
> already mentioned above can be used. If this RFC succeeds at adding it
> as core syntax, a `Feature::Compat::Defer` module can be created for it
> in the same style as
> [`Feature::Compat::Try`](https://metacpan.org/pod/Feature::Compat::Try).
>
> ## Security Implications
>
> None foreseen.
>
> ## Examples
>
> A couple of small code examples are quoted above. Further, from the
> docs of `Syntax::Keyword::Defer`:
>
> use feature 'defer';
>
> {
> my $dbh = DBI->connect( ... ) or die "Cannot connect";
> defer { $dbh->disconnect; }
>
> my $sth = $dbh->prepare( ... ) or die "Cannot prepare";
> defer { $sth->finish; }
>
> ...
> }
>
> ## Prototype Implementation
>
> CPAN module `Syntax::Keyword::Defer` as already mentioned.
>
> In addition, I have a mostly-complete branch of bleadperl (somewhat
> behind since I haven't updated it for the 5.34 release yet) at
>
> https://github.com/leonerd/perl5/tree/defer
>
> I'm not happy with the way I implemented it yet (don't look at how I
> abused an SVOP to store the deferred optree) - it needs much tidying up
> and fixing for the various things I learned while writing the CPAN
> module version.
>
> ## Future Scope
>
> If this RFC becomes implemented, it naturally follows to enquire
> whether the same mechanism that powers it could be used to add a
> `finally` clause to the `try/catch` syntax added in Perl 5.34. This
> remains an open question: while it doesn't any new ability, is the
> added expressive power and familiarity some users will have enough to
> justify there now being two ways to write the same thing?
>
> ## Rejected Ideas
>
> On the subject of naming, this was originally called `LEAVE {}`, which
> was rejected because its semantics don't match the Raku language
> feature of the same name. It was then renamed to `FINALLY {}` where it
> was implemented on CPAN. This has been rejected too on grounds that
> it's too similar to the proposed `try/catch/finally`, and it shouldn't
> be a SHOUTY PHASER BLOCK. All the SHOUTY PHASER BLOCKS are declarations,
> activated by their mere presence at compiletime, whereas `defer {}` is
> a statement which only takes effect if dynamic execution actually
> encounters it. The p5p mailing list and the CPAN module's RT queue both
> contain various other rejected naming ideas, such as UNWIND, UNSTACK,
> CLEANUP, to name three.
>
> Another rejected idea is that of conditional enqueue:
>
> defer if (EXPR) { BLOCK }
>
> as this adds quite a bit of complication to the grammar, for little
> benefit. As currently the grammar requires a brace-delimited block to
> immediately follow the `defer` keyword, it is possible that other ideas
> - such as this one - could be considered at a later date however.
>
> ## Open Issues
>
> Design-wise I don't feel there are any remaining unresolved questions.
>
> Implementation-wise the code still requires some work to finish it off;
> it is not yet in a merge-ready state.
>
> ## Copyright
>
> Copyright (C) 2021, Paul Evans.
> This document and code and documentation within it may be used,
> redistributed and/or modified under the same terms as Perl itself.
>
> --
> Paul "LeoNerd" Evans
>
> leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
> http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, 17 Jun 2021 08:57:20 +0900
Yuki Kimoto <kimoto.yuki@gmail.com> wrote:

> Personally, I think defer block syntax is difficult to implement.

Hm? I've 95% finished implementing it. It was easy.

> Especially the relation of
>
> 1. GC
> 2. Destructor
>
> The relation of GG and block of Current Perl is simple. an object is
> released at the end of the block if the object reference count is 0.
>
> defer syntax maybe add complex things of this relation.
>
> This feature seems to be planned for the finally block of try catch
> syntax.

Indeed; I can reüse about 90% of the implementation of `defer` to add a
`try/catch/finally`.

> Are there other strong benefits?

Mostly those.

--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: RFC 0004 - defer {} syntax [ In reply to ]
Ben Bullock <benkasminbullock@gmail.com> wrote:
>
> I can see an advantage from being able to run defers conditionally.

consider:

defer if EXPR { ... }
vs:
defer { ... if EXPR }

The second form is already allowed by the syntax proposed in this RFC, so the benefit of also allowing the first form seems to be little.


--
Arne Johannessen
<https://arne.johannessen.de/>
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, 17 Jun 2021 12:16:01 +0300
Sergey Aleynikov <sergey.aleynikov@gmail.com> wrote:

> - why invent a new verb for already existing semantic?
> - what do all current CPAN implementations lack, thus requiring this
> feature?
> - why has this to be a core feature, compared to yet another CPAN
> module?
> - why any existing module can't be bundled with Perl, achieving this
> functionality?

First I'm going to quote Nicholas Clark's lovely reply to this on a
different thread:

>> Trouble is, that Perl is a Turing complete language. Meaning that
>> *any* possible new feature that could be proposed can already be
>> implemented with existing functionality.

>> So arguing against a new feature because it is already possible is,
>> well, tautological.

Secondly I'm going to point out a few things:

* Aside from Syntax::Keyword::Defer, every other solution to this in
CPAN all wraps the deferred code in an anon sub. Core's defer, and
S-K-D do not. This has many effects but means, for example, that @_
behaves "unexpectedly"; and caller() gets confused.

* Additionally, all of the CPAN implementations besides S-K-D
implement the deferred code by either DESTROY on an object, or using
SAVEDESTRUCTOR_X to call_cv() an anon CV later on. In both of these
cases, the deferred code could throw an exception, which by
necessity is non-fatal to the surrounding code and is either
ignored, or simply printed with a warning. S-K-D, and my core
implementation, do not do this. They nest an inner RUNOPS loop,
meaning that exceptions behave "as expected" - a `die` inside
`defer` is thrown to the caller much like a `die` anywhere else.

* All of the CPAN implementations, including S-K-D, have unfortunate
interactions between next/last/redo (the "loopex" ops) and the defer
block. A true core implementation can forbid these.

while(1) {
defer { last }
}

will cause core to die with "Unable to 'last' out of a defer block"

whereas, any of the CPAN versions will either permit it but cause
further confusions, or outright segfault the interpreter.

In summary, *all* of the CPAN solutions are worse than core can
provide, because core can do things no CPAN solution (including
pluggable keywords) can do. Specifically in this case, the core
solution adds a whole new context stack type (CXt_DEFER) that the
loopex ops can inspect to complain about trying to next/last/redo
out of. Also `goto`.

Thirdly, to answer your final point about why not just bundle the syntax
module with core perl: If the module were able to *perfectly* perform
the same work as core perl, this could technically work. However, note
above that it can't (CXt_DEFER). But even if this were possible, I
don't think that is a very nice way forward. The entire point of
pluggable syntax modules is to allow CPAN-based experimentation of new
features (Function::Parameters became feature 'signature';
Syntax::Keyword::Try became feature 'try'). The successful end-goal of
that experimentation is promoting the feature to becoming true core
syntax. There is no point in core shipping a syntax plugin module,
because core can just do core things.

This third point will always be my answer whenever anyone says "well
why can't we just ship that syntax module with core".

--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: RFC 0004 - defer {} syntax [ In reply to ]
Overall here I'm seeing a lot of feedback about the idea, but none
really about the way I have documented it. I thought that was the point
of this pre-RFC "just send an email" part of the process.

Since folks are engaging with the substance of the document, I will
judge that I've got the first draft "about right", and therefore add it
to the RFCs repo:

https://github.com/Perl/RFCs/pull/2

--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: RFC 0004 - defer {} syntax [ In reply to ]
On 17/06/2021, Arne Johannessen wrote:

> Ben Bullock <benkasminbullock@gmail.com> wrote:
>>
>> I can see an advantage from being able to run defers conditionally.

> consider:

> defer if EXPR { ... }
> vs:
> defer { ... if EXPR }

> The second form is already allowed by the syntax proposed in this RFC,
> so the benefit of also allowing the first form seems to be little.

This first reads to me as implying that if EXPR is evaluated at the point
where the defer statement occurs, whereas the second if EXPR is clearly
evalauted at "defer time" .

Regards
Re: RFC 0004 - defer {} syntax [ In reply to ]
??????? Original Message ???????

On Thursday, June 17th, 2021 at 6:58 AM, Paul "LeoNerd" Evans <leonerd@leonerd.org.uk> wrote:

> Overall here I'm seeing a lot of feedback about the idea, but none
>
> really about the way I have documented it. I thought that was the point
>
> of this pre-RFC "just send an email" part of the process.
>
> Since folks are engaging with the substance of the document, I will

Sounds like a good idea to me. I suppose I was wrong; seems this was actually a pre-RFC (was not clear so that needs to be a tweak in the process - I have been trying to follow along).

Question for PSC: what's the indicator for submitting such a request? Could this have been done earlier in the "discussion"? What sort of discussions/comments are even appropriate for a pre-RFC? Seems to me as long as it's "well formed" and "high quality" the burden for creating a PR should be pretty low, since this is not the stage where the details I've been seeing in this thread are to be 'hashed out'.

>
> judge that I've got the first draft "about right", and therefore add it
>
> to the RFCs repo:
>
> https://github.com/Perl/RFCs/pull/2

So now I'll ask the RFC mediators; what's the next step here?

Brett

>
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Paul "LeoNerd" Evans
>
> leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
>
> http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: RFC 0004 - defer {} syntax [ In reply to ]
i like "ON_EXIT" better as a name for the block.

Also I published a working mechanism for this to CPAN, which hooks into the
local variable restoration through tie magic.

https://metacpan.org/pod/Scope::local_OnExit



On Thu, Jun 17, 2021 at 7:16 AM mah.kitteh via perl5-porters <
perl5-porters@perl.org> wrote:

>
> ??????? Original Message ???????
>
> On Thursday, June 17th, 2021 at 6:58 AM, Paul "LeoNerd" Evans <
> leonerd@leonerd.org.uk> wrote:
>
> > Overall here I'm seeing a lot of feedback about the idea, but none
> >
> > really about the way I have documented it. I thought that was the point
> >
> > of this pre-RFC "just send an email" part of the process.
> >
> > Since folks are engaging with the substance of the document, I will
>
> Sounds like a good idea to me. I suppose I was wrong; seems this was
> actually a pre-RFC (was not clear so that needs to be a tweak in the
> process - I have been trying to follow along).
>
> Question for PSC: what's the indicator for submitting such a request?
> Could this have been done earlier in the "discussion"? What sort of
> discussions/comments are even appropriate for a pre-RFC? Seems to me as
> long as it's "well formed" and "high quality" the burden for creating a PR
> should be pretty low, since this is not the stage where the details I've
> been seeing in this thread are to be 'hashed out'.
>
> >
> > judge that I've got the first draft "about right", and therefore add it
> >
> > to the RFCs repo:
> >
> > https://github.com/Perl/RFCs/pull/2
>
> So now I'll ask the RFC mediators; what's the next step here?
>
> Brett
>
> >
> >
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> >
> > Paul "LeoNerd" Evans
> >
> > leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
> >
> > http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
>


--
"Lay off that whiskey, and let that cocaine be!" -- Johnny Cash
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, 17 Jun 2021 at 21:32, David Nicol <davidnicol@gmail.com> wrote:

> i like "ON_EXIT" better as a name for the block.
>

Two issues I'd raise with this suggestion:

- the RFC explicits covers reasons not to use ALL-CAPS for naming.
Alternative suggestions should at least address those reasons with
counterpoints?
- it's triggered by scope end, not exit... so we'd be inconsistent with
both exit() (the keyword), and END (the phaser)
Re: RFC 0004 - defer {} syntax [ In reply to ]
it's a phaser, phasers are all-caps, whatever it's called. BLOCK_EXIT
maybe. My other often wished-for phaser is ONCE.

On Thu, Jun 17, 2021 at 9:01 AM Tom Molesworth <tom@deriv.com> wrote:

>
> - it's triggered by scope end, not exit... so we'd be inconsistent with
> both exit() (the keyword), and END (the phaser)
>
>
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, Jun 17, 2021 at 7:07 PM David Nicol <davidnicol@gmail.com> wrote:

> it's a phaser, phasers are all-caps, whatever it's called. BLOCK_EXIT
> maybe. My other often wished-for phaser is ONCE.
>

Is it? I would have expected a phaser to take (some) effect at compile
time. As would the RFC, it seems: " All the SHOUTY PHASER BLOCKS are
declarations, activated by their mere presence at compiletime, whereas
`defer {}` is a statement which only takes effect if dynamic execution
actually encounters it."

But I'm not sure what we should expect. What even is a phaser in Perl
5? Is this the occasion to land a definition?

(In Raku a phaser is documented to be a trait of a closure. It's not
clear to me if they could be applied after compilation, but they certainly
don't do so out of the box.)


Eirik
Re: RFC 0004 - defer {} syntax [ In reply to ]
and at compile time, a SCOPE_EXIT phaser would attach its coderef to the
exiting of the scope, encountered during run-time flow or not. Or
alternately, activated by having encountered it during run-time flow.
SCOPE_EXIT blocks if any (are active) would run before unlocalization of
package vars.


On Thu, Jun 17, 2021 at 12:35 PM Eirik Berg Hanssen <
Eirik-Berg.Hanssen@allverden.no> wrote:

> On Thu, Jun 17, 2021 at 7:07 PM David Nicol <davidnicol@gmail.com> wrote:
>
>> it's a phaser, phasers are all-caps, whatever it's called. BLOCK_EXIT
>> maybe. My other often wished-for phaser is ONCE.
>>
>
> Is it? I would have expected a phaser to take (some) effect at compile
> time. As would the RFC, it seems: " All the SHOUTY PHASER BLOCKS are
> declarations, activated by their mere presence at compiletime, whereas
> `defer {}` is a statement which only takes effect if dynamic execution
> actually encounters it."
>
> But I'm not sure what we should expect. What even is a phaser in Perl
> 5? Is this the occasion to land a definition?
>
> (In Raku a phaser is documented to be a trait of a closure. It's not
> clear to me if they could be applied after compilation, but they certainly
> don't do so out of the box.)
>
>
> Eirik
>


--
"Lay off that whiskey, and let that cocaine be!" -- Johnny Cash
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, 17 Jun 2021 14:18:02 -0500
David Nicol <davidnicol@gmail.com> wrote:

> and at compile time, a SCOPE_EXIT phaser would attach its coderef to
> the exiting of the scope, encountered during run-time flow or not. Or
> alternately, activated by having encountered it during run-time flow.
> SCOPE_EXIT blocks if any (are active) would run before unlocalization
> of package vars.

"code ref"

"encountered or not"

You seem to be describing a different thing that is not `defer {}`.

--
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
Re: RFC 0004 - defer {} syntax [ In reply to ]
really? you've said "defer <CODEREF>" repeatedly, and it sounded like the
message I was responding to brought up the question of if defered block
would happen always, or only when the defer keyword was encountered during
run-time flow.

You did not dispute my claim that local::on_scope_exit (or whatever I
called that CPAN module) does essentially the same thing.

"defer" is a mechanism for running something after the exiting of a code
block, a/k/a scope, but before the invoking code gets the returned value
back, is it not?

On Thu, Jun 17, 2021 at 2:25 PM Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>
wrote:

> On Thu, 17 Jun 2021 14:18:02 -0500
> David Nicol <davidnicol@gmail.com> wrote:
>
> > and at compile time, a SCOPE_EXIT phaser would attach its coderef to
> > the exiting of the scope, encountered during run-time flow or not. Or
> > alternately, activated by having encountered it during run-time flow.
> > SCOPE_EXIT blocks if any (are active) would run before unlocalization
> > of package vars.
>
> "code ref"
>
> "encountered or not"
>
> You seem to be describing a different thing that is not `defer {}`.
>
> --
> Paul "LeoNerd" Evans
>
> leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS
> http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/
>


--
"Lay off that whiskey, and let that cocaine be!" -- Johnny Cash
Re: RFC 0004 - defer {} syntax [ In reply to ]
On Thu, 17 Jun 2021 14:35:27 -0500
David Nicol <davidnicol@gmail.com> wrote:

> really? you've said "defer <CODEREF>" repeatedly

I hope I haven't ever said that. I have mentioned `defer { BLOCK }`.

Blocks aren't coderefs.

E.g. consider the way that if(COND) { BLOCK } is not a coderef.

It's verymuch not a coderef - `caller()` can't see it, it doesn't get
its own `@_`, you can't `return` from it, etc...

--
Paul "LeoNerd" Evans

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

1 2 3  View All