Problem-solving: `once` block can be executed more than once

Created on 16 Jun 2019  Â·  10Comments  Â·  Source: Raku/problem-solving

Say you have this piece of code:

sub foo { once { say ‘once!’ } };
foo; foo; foo

Output:

once!

So far so good. Then you decide to add a loop into the sub body:

sub foo { for ^5 { once { say ‘once!’ } } };
foo; foo; foo

Output:

once!
once!
once!

So a once block can be executed more than once and intuitively it hardly makes any sense.

Looking at the ecosystem, implementing logic for things that should only be done once is better done with a variable:

language

Most helpful comment

we still have a once block that can be executed more than once

Just like once in natural language is also interpreted according to the surrounding context. If I tell you to "give me feedback once per week", I'm not going to be too impressed when you only give me feedback once ever, or - worse - just repeat to me the feedback you gave in the first week every time.

All 10 comments

IRC discussion: https://colabti.org/irclogger/irclogger_log/perl6?date=2019-06-16#l502

Basically, @ugexe++ points out that the examples in the original post implement logic that actually lets things run more than once. I guess the takeaway is that it is useful to have a once per something construct, where something should be either explicit or obvious.

once runs once per closure clone.

for ^3        # <- this gets run 1 time
{             # <- this is a closure that gets cloned 1 time, and run 3 times

    for ^5    # <- this gets run three times
    {         # <- so this closure gets cloned 3 times, and run 5 times per clone

        once say 42 # <- So the code in `once` gets run 3 times
    }
}

The above is effectively short for this:

for ^3
{

    my $__once_00001 = 0;

    for ^5
    {

        (say 42) if ($__once_00001++ == 0);
    }
}

This is the most sensible default.

If it didn't:

  • Every use of it would slow the program down (locks and globals that are outside of the CPU cache).
  • It would keep data in memory that could otherwise be garbage collected, on the off chance it needs the result again.
    That could include other things in the lexical environment, if the result is a closure itself.
    It could also prevent the Partial Escape Analysis from doing its job as effectively.
  • It would be less useful.
    I would suspect that most uses of it [outside of globals] rely on the result eventually changing, or the result eventually getting garbage collected.
  • It would break old code.
  • It would add a global for every once even though one of the design goals of Perl6 was to reduce and remove globals.

It confuses people most when they use it exclusively for side effects.

I see it as a way to get the result from an expression, while running that expression as few times as necessary.
That is it is as more or less a local optimization.

loop ( my $i = 0; $i < once $v.some-slow-method(); ++$i ){
    …
}

I would bet that everyone who is confused about once is also confused about state variables.
Which makes sense since they are fundamentally the same feature.
once is just a wrapper for state in a similar way that start is a wrapper for Promise.start() which is a wrapper for $*SCHEDULER.cue().

once $_ + 1

(state $ = $_ + 1)

If someone wants code run exactly once per process, they should either use INIT or wrap their once inside of a global subroutine with something like a Lock.protect({…}) surrounding it.

That is the only reliable way to make sure it runs exactly once.

@AlexDaniel I think the exhaustive explanation by @b2gills makes this issue resolved, isn't it? Besides, it is now well-documented too, as I see here.

@vrurg we still have a once block that can be executed more than once. No amount of excuses can fix that. I'm optimistic, if even git devs are lately fixing the user interface, I hope we can one day take a peek at the language from outside the bubble and start thinking how things should actually be.

The way to have once run exactly once is to rename it BEGIN or INIT or similar.

Everytime I wrote once it needed to be setup to run once per closure clone. Whether I knew it at the time or not.

role Singleton {
  method new (|) {
    once callsame
  }
}

class Foo does Singleton { submethod TWEAK (){ say 'Initialize Foo'}}
class Bar does Singleton { submethod TWEAK (){say 'Initialize Bar'}}

Foo.new; # «Initialize Foo»
Foo.new; # «»

Bar.new; # «Initialize Bar»
Bar.new; # «»

Short of renaming the feature or removing it entirely, I don't know what you want us to do.

We can't just have it run exactly once without either risking race conditions, or slowing everything down by adding some locking construct.
I suppose it could automatically determine which of the phasers it has to run in to get it to run correctly. (In which case it should be uppercased.)


The thing is once is a lightweight feature.
You seem to want it to not be that.


I mean sure it may be confusing at first, but there are a lot of features of the language that are confusing at first.

I mean someone could be confused that say doesn't make any sound.
I would bet that if Microsoft ever added say to VBScript that it would use a text-to-speech engine.


If you read the apocalypses, you will find that Larry considered it ok to occasionally confuse new programmers, but it was not ok to confuse an expert.
It doesn't confuse me, and I don't consider myself that much of an expert.

If it did start using locks or automatically choosing an appropriate phaser, it would probably start confusing me.
(It would also break just about every bit of my code that used it.)

Short of renaming the feature or removing it entirely, I don't know what you want us to do.

Yeah, some sort of deprecation will be needed. There are many ways to do it and we can discuss it once we're on the same page.

Everytime I wrote once it needed to be setup to run once per closure clone. Whether I knew it at the time or not.

Well, two examples in the first post show that sometimes once does not cut it. Your experience is probably different, and that's ok, but I've seen enough questions about this feature on #perl6.

We can't

Are you sure?

The thing is once is a lightweight feature.
You seem to want it to not be that.

Don't put words into my mouth. Also, I'm pretty sure it can remain a lightweight feature even if locks are used. My understanding is that you only need some sort of a lock (or atomic) to kick off the first execution, after that the state is not going to change. Once the flag is set it's not going to ever change, so you no longer need anything to prevent race conditions and such. Basically you're sacrificing a bit of performance for the first execution, not much else. In my opinion that's a very fair tradeoff, if it's a tradeoff at all. Can we stop this thing with excuses, please?

I mean sure it may be confusing at first, but there are a lot of features of the language that are confusing at first.

I know :(

ok to occasionally confuse new programmers, but it was not ok to confuse an expert

That's a very low bar :(

Ah, to make it clear, once-ever is also not what people seem to need most of the time, so it's a bit more complicated. Intuitively once-per-sub is what can eliminate most hacked up $once solutions, but I'm not entirely sure.

we still have a once block that can be executed more than once

Just like once in natural language is also interpreted according to the surrounding context. If I tell you to "give me feedback once per week", I'm not going to be too impressed when you only give me feedback once ever, or - worse - just repeat to me the feedback you gave in the first week every time.

It feels to me like it was discussed somewhere and an idea popped up about using adverbs like once { ... }, :per-sub.

Was this page helpful?
0 / 5 - 0 ratings