Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Closure trait for loop entry

9 views
Skip to first unread message

Joe Gottman

unread,
Feb 12, 2005, 10:22:22 AM2/12/05
to perl6-l...@perl.org
Often when I write a loop I want to run some code at loop entry time. It
would be nice to have a closure trait for this, similar to NEXT for loop
continuation or LAST for loop termination, but there isn't one. I don't
think either FIRST or ENTER do quite what I want. FIRST runs only once,
which is too few times, and ENTER runs every time the loop block is entered,
which is too many.

To demonstrate what I want, consider the following examples:

sub use_first()
{
for 1..2 {
FIRST {say 'entering loop';}
say $_;
LAST {say 'leaving loop';}
}
}

sub use_enter()
{
for 1..2 {
ENTER {say 'entering loop';}
say $_;
LAST {say 'leaving loop';}
}
}


The first time use_first is called it will print
entering loop
1
2
leaving loop

but subsequently it will print
1
2
leaving loop

and leave out the 'entering loop'.

When use_enter is called it will print
entering loop
1
entering loop
2
leaving loop

I want to output
entering loop
1
2
leaving loop

every time I run my loop. I suggest we create a new closure trait called
SETUP for this. Then the code could read

for 1..2 {
SETUP {say 'entering loop';}
say $_;
LAST {say 'leaving loop';}
}

Since SETUP can be used for initialization, it should probably be allowed
within an expression:
state $first_iteration = SETUP {true} will next {$_ = false};

Lower case 'setup' could probably also be used as a trait on a variable
state $first_iteration will setup {$_ = true} will next {$_ =
false};

Joe Gottman


Uri Guttman

unread,
Feb 12, 2005, 12:44:05 PM2/12/05
to jgot...@carolina.rr.com, perl6-l...@perl.org
>>>>> "JG" == Joe Gottman <jgot...@carolina.rr.com> writes:

JG> sub use_first()
JG> {
JG> for 1..2 {
JG> FIRST {say 'entering loop';}
JG> say $_;
JG> LAST {say 'leaving loop';}
JG> }
JG> }

JG> The first time use_first is called it will print
JG> entering loop
JG> 1
JG> 2
JG> leaving loop

JG> but subsequently it will print
JG> 1
JG> 2
JG> leaving loop

that seems to imply that FIRST is a program level first time action. i
think it does what you want and is executed on the first iteration of a
loop, each time the loop is started up. it is symmetrical to LAST in
that way. it should print the former text each time the sub is called.

but i could be wrong. :)

uri

--
Uri Guttman ------ u...@stemsystems.com -------- http://www.stemsystems.com
--Perl Consulting, Stem Development, Systems Architecture, Design and Coding-
Search or Offer Perl Jobs ---------------------------- http://jobs.perl.org

Larry Wall

unread,
Feb 12, 2005, 2:33:21 PM2/12/05
to perl6-l...@perl.org
On Sat, Feb 12, 2005 at 12:44:05PM -0500, Uri Guttman wrote:
: >>>>> "JG" == Joe Gottman <jgot...@carolina.rr.com> writes:
:
: JG> sub use_first()
: JG> {
: JG> for 1..2 {
: JG> FIRST {say 'entering loop';}
: JG> say $_;
: JG> LAST {say 'leaving loop';}
: JG> }
: JG> }
:
: JG> The first time use_first is called it will print
: JG> entering loop
: JG> 1
: JG> 2
: JG> leaving loop
:
: JG> but subsequently it will print
: JG> 1
: JG> 2
: JG> leaving loop
:
: that seems to imply that FIRST is a program level first time action. i
: think it does what you want and is executed on the first iteration of a
: loop, each time the loop is started up. it is symmetrical to LAST in
: that way. it should print the former text each time the sub is called.
:
: but i could be wrong. :)

What's going on here is that the loop body is a closure that is
cloned upon entry to the loop (you're logically passing a closure
to the "for()" function that implements the loop), so if there's a
FIRST inside, it runs each time the loop is initialized. Likewise
state variables are logically regenerated for closure clones, so if
use_first() above wants to have a state variable that is maintained
from call to call, it must put it *outside* the loop.

Also, note that a LAST block, if any, has to be called from within
the implementation of for(), since only for() knows when it's done
with the loop as a whole.

It will be an interesting problem for the optimizer to figure out
how to avoid cloning closures that are passed only to synchronous
loop-controller functions such as for() and not otherwise used
asynchronously. Perhaps the signature of for() can make some
guarantees about not squirreling away the closure pointer in
some weird place. This is perhaps related to the issue of timely
GC on pointers you know haven't been copied into outside storage.

Larry

Uri Guttman

unread,
Feb 12, 2005, 3:55:40 PM2/12/05
to perl6-l...@perl.org
>>>>> "LW" == Larry Wall <la...@wall.org> writes:

LW> : JG> The first time use_first is called it will print
LW> : JG> entering loop
LW> : JG> 1
LW> : JG> 2
LW> : JG> leaving loop
LW> :
LW> : JG> but subsequently it will print
LW> : JG> 1
LW> : JG> 2
LW> : JG> leaving loop
LW> :
LW> : that seems to imply that FIRST is a program level first time action. i
LW> : think it does what you want and is executed on the first iteration of a
LW> : loop, each time the loop is started up. it is symmetrical to LAST in
LW> : that way. it should print the former text each time the sub is called.

LW> What's going on here is that the loop body is a closure that is
LW> cloned upon entry to the loop (you're logically passing a closure
LW> to the "for()" function that implements the loop), so if there's a
LW> FIRST inside, it runs each time the loop is initialized. Likewise
LW> state variables are logically regenerated for closure clones, so if
LW> use_first() above wants to have a state variable that is maintained
LW> from call to call, it must put it *outside* the loop.

i am not clear on the actual answer. my take on what you said is that i
was right, FIRST will execute at the beginning of each time the loop is
entered which is what joe wants. am i correct?

LW> Also, note that a LAST block, if any, has to be called from within
LW> the implementation of for(), since only for() knows when it's done
LW> with the loop as a whole.

similarly for FIRST as only for() knows when it starts up the loop again
and reinitializes the counter.

LW> It will be an interesting problem for the optimizer to figure out
LW> how to avoid cloning closures that are passed only to synchronous
LW> loop-controller functions such as for() and not otherwise used
LW> asynchronously. Perhaps the signature of for() can make some
LW> guarantees about not squirreling away the closure pointer in
LW> some weird place. This is perhaps related to the issue of timely
LW> GC on pointers you know haven't been copied into outside storage.

i see the problem. if the loop body refers to lexicals declared outside
it, it looks like a classic perl5 closure and would need to be
cloned. what about the fact that for() will be called in effectively a
void context? a classic closure makes sense only when the code ref is
saved or possible called immediately via (p5) ->. for() is called
immediately but with a different signature as you said. the void context
would help the optimizer since you don't save the code block for later
reuse, no cloning should be done.

Larry Wall

unread,
Feb 12, 2005, 4:35:11 PM2/12/05
to perl6-l...@perl.org
On Sat, Feb 12, 2005 at 03:55:40PM -0500, Uri Guttman wrote:
: LW> What's going on here is that the loop body is a closure that is

: LW> cloned upon entry to the loop (you're logically passing a closure
: LW> to the "for()" function that implements the loop), so if there's a
: LW> FIRST inside, it runs each time the loop is initialized. Likewise
: LW> state variables are logically regenerated for closure clones, so if
: LW> use_first() above wants to have a state variable that is maintained
: LW> from call to call, it must put it *outside* the loop.
:
: i am not clear on the actual answer. my take on what you said is that i
: was right, FIRST will execute at the beginning of each time the loop is
: entered which is what joe wants. am i correct?

Yes.

: LW> Also, note that a LAST block, if any, has to be called from within


: LW> the implementation of for(), since only for() knows when it's done
: LW> with the loop as a whole.
:
: similarly for FIRST as only for() knows when it starts up the loop again
: and reinitializes the counter.

I think FIRST initialization happens automatically based on the cloning
mechanism, and the fact that FIRST knows when it's first already. The
code of a FIRST can actually be called inline, I think. Could be wrong
about that.

: LW> It will be an interesting problem for the optimizer to figure out


: LW> how to avoid cloning closures that are passed only to synchronous
: LW> loop-controller functions such as for() and not otherwise used
: LW> asynchronously. Perhaps the signature of for() can make some
: LW> guarantees about not squirreling away the closure pointer in
: LW> some weird place. This is perhaps related to the issue of timely
: LW> GC on pointers you know haven't been copied into outside storage.
:
: i see the problem. if the loop body refers to lexicals declared outside
: it, it looks like a classic perl5 closure and would need to be
: cloned. what about the fact that for() will be called in effectively a
: void context? a classic closure makes sense only when the code ref is
: saved or possible called immediately via (p5) ->. for() is called
: immediately but with a different signature as you said. the void context
: would help the optimizer since you don't save the code block for later
: reuse, no cloning should be done.

Doesn't really help--when you think about it, the block you pass to
map or grep is also a loop block, and those generally aren't used in
void context. It would have to be a marker on the actual block argument
in the signature. Maybe based on type:

sub mygrep (Block &block, *@list) {...} # don't clone
sub mysched (Closure &block, *@list) {...} # clone

Since the Block declaration would mostly be an optimizer hint,
I suspect the default should be to clone, so the latter can just
be written:

sub mysched (&block, *@list) {...} # clone

Actual type names are negotiable, of course. Maybe typename is the
wrong place for that info, and it should just be

sub mygrep (&block is uncloned, *@list) {...} # don't clone

Or maybe it's just spelled with existing props:

sub mygrep (&block is ref, *@list) {...} # don't clone
sub mygrep (&block is copy, *@list) {...} # clone (default)

Larry

0 new messages