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:
Traps to avoid: https://docs.perl6.org/language/traps#Using_an_once_block
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:
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.
Most helpful comment
Just like
oncein 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.