New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unclear sharing of $/
causing race conditions
#406
Comments
Massive +1 for eliminating this "action at a distance" in the future (whether it's as soon as 6.e or later on). For setting (and only that - not reading!) I don't feel strongly about setting I could even imagine a pragma for these implicit "magic variables" that are set in operations, something like |
grammar G { token TOP { (\w+) } } # add () to set $0
sub summarize() {
("aaa" .. "zzz").race.map( {
G.parse($_);
$0.Str.comb.Set.keys.ords.sum # something to keep CPU busy
} ).sum
}
say summarize; # 6615297
Sadly my testing was bogus, probably because I used a |
@lizmat does this effectively mean that all matchings should create their own |
Further golf of the code: say ("aaa" .. "zzz").race.map({
/ \w+ /;
$/.Str.comb.Set.keys.ords.sum
} ).sum So note that this problem is actually more general, and not limited to using grammars. |
Is the problem because of the user-space access of |
Don't see any reason a user should set |
A similar situation may actually make sense: $_ = "foo";
if 42 {
m/ \w+ /;
}
else {
m/ \d+ /;
}
say $/; # 「foo」 However, there are a few strange things happening here. If we remove the |
Sharpening my understanding. Is the following a correct description of the situation? |
I'd firmly classify this as a bug. I would have expected for $/ to be |
@patrickbkr Yes, that is a bug. Fortunately, this is already fixed in RakuAST, so I won't be spending time on trying to fix this in the legacy grammar. |
After discussing this with @jnthn, a lot has become clearer to me. Basically, the functionality of I didn't really grok this, and will probably adapt the text to make it clearer in what it does. The pseudo code for matching is basically:
What This, combined with the fact that blocks do not declare a lexical Now, this is a very elegant design that works like a charm in a single-threaded world. But in a multi-threaded world, this is a nasty WAT. For example: start { "foo" ~~ / \w+ / }
sleep 1; # give thread startup a little time
say $/; # Nil Huh? Why doesn't that say Promise.start: { "foo" ~~ / \w+ / }
sleep 1; # give thread startup a little time
say $/; # 「foo」 Isn't that supposed to be the same? Yes, it is, but the grammar handling of start { say MY::.keys } # ($/ $_ $!)
sleep 1; # give thread startup a little time versus: Promise.start: { say MY::.keys } # ($_)
sleep 1; # give thread startup a little time If there is a keyword in the Raku grammar that allows for special compile time tweaking of the opcode, it can be done. However, in the case of Conclusion: I think we need to document this behaviour of Going back to the golfed code, adding a say ("aaa" .. "zzz").race.map({
my $/ = / \w+ /; # define lexical $/ to prevent race condition
$/.Str.comb.Set.keys.ords.sum
} ).sum is a simple workaround that I believe can be easily explained and documented. |
I think this issue is getting fragmented. The original issue that I responded to was about The more recent comments were about how Let's decide which issue is the one discussed here. Another issue can be opened for the other phenomenon. (Honestly, since the original code snippet doesn't have |
The title stated the subject. The |
Do we need a |
@jubilatious1: no |
The problem was less the setting of |
I would stick to the state of affairs where in 6.e As far as I understand, most concern with removal of if /.../ -> $m { say $m<match> } But as long as we give special meaning to the topic variable, we can give it to if /.../ -> $/ { say $<match> } If things go for dropping it out then I wouldn't care much about the bugs in 6.c and 6.d. They could be documented for the sake of clarity. If sub foo {
# my $/
if condition() {
# no $/
@a.map({ #`{ my $/ } /.../ });
}
} I.e. the block within map gets its own lexical This behavior would be easier to document and explain. It wouldn't fix cases like Though, as a side note, the above snippet with Speaking of grammars, to support them the compiler must now that To conclude, it looks a lot like trying to fix it doesn't worth the time and the energy spent. |
As we do on action methods... |
FWIW, from a performance point of view, I would be very much in favour of not setting "foo".match(/.+/);
say $/; # 「foo」
say "FOO".subst(/.+/, "bar"); # bar
say $/; # 「FOO」 But it will break a lot of code out there :-( |
@lizmat make it a 6.e? |
@lizmat said:
Changing https://unix.stackexchange.com/users/227738/jubilatious1 No way to throw an error on hyper/race code that touches |
@jubilatious1: would that code also be broken if Also, I wonder how many people know that |
IMHO, you're proposing eliminating the "Baby Raku" that I 'grew up' learning. I can look back in my history and find REPL examples of the variety: ~ % raku
Welcome to Rakudo™ v2023.05.
Implementing the Raku® Programming Language v6.d.
Built on MoarVM version 2023.05.
To exit type 'exit' or '^D'
[0] > say $/ if "cow" ~~ "cow";
Nil
[0] > say $/ if "cow" eq "cow";
Nil
[0] > say $/ if "cow".match(/cow/);
「cow」
[0] > say $/ if "cow".match(/co./);
「cow」
[0] > say $/ if "cow".match(/co(.)/);
「cow」
0 => 「w」
[0] > say $/ if "cow".match(/cot/);
() Efficiency isn't everything: you need entree-points into the language. Otherwise, how will people learn the Raku regex-engine? Finally, aren't you worried about unintentional breakage? Will something like the following still work without setting raku -ne 'BEGIN my $i; ++$i for m:g/ … /; END $i.say;' (FYI, the bare-bones |
What do you mean by "magic"? What difference do you mean when you separate "magic" from "magical action"? In particular, I know what I mean by those words, and would appreciate you explaining why you would disagree with what I think they mean:
Following the principle you suggest, we should consider dropping the above magic for 6.e. I for one think we should consider doing so. (I personally think magic can have its place, and imo sufficiently tried and trusted magic should not be reduced just because it's magic. That said, imo it's a hot prospect for the chopping block if instead it's sufficiently tried and distrusted.) Among many possible motivations for going beyond mere consideration of such minimization is that we need to either double down on the existing magic to make the magic thread safe or we need to adequately address the downsides of the magic -- and we need to allow that there may be no adequate way to address the downsides, and 6.e may well be an opportunity to make a break we need to make. Note that that is about code randomly being silently incorrect, or crashing, depending on indeterminate variations between runs. It's not about having good ergonomics, which is nice to have, nor is it about good performance or efficiency, which is also nice to have, but is instead just about straight up the worst problem of all -- code that silently unpredictably does the wrong thing due to magic. |
@raiph , not sure if I have an answer for you yet. However, my experience tells me this is easy: Sort a list of words by last letter: ~ % echo 'cat dog horse' | raku -ne '.say for .words.sort: *.match(/ .$/);'
horse
dog
cat This is less easy, I'm clearly going for a two column linewise output of "word, last-letter" here. But I don't get it: ~ % echo 'cat dog horse' | raku -ne 'my @a = .words; say ($_ , $/) for @a.map: *.match(/ .$/);'
(「t」 「t」)
(「g」 「g」)
(「e」 「e」) So I re-configure, but (again, fiddly) I have to parens the ~ % echo 'cat dog horse' | raku -ne 'my @a = .words; for @a { say "$_ ", ($/ given .match(/ .$/))};'
cat 「t」
dog 「g」
horse 「e」 So maybe the take-home is if you give users access to a variable such as [[ Extra Credit: now sort the last codeblock in last-letter order. ]] |
Two overall points. If you don't understand something, it's good to only use it to try understand it. And if something matches your expectations, that itself doesn't mean it should. (I've written both these points in a manner that may make it unclear what it is I'm talking about. And that is a key part of the point I'm making. If you're confused, consider just reading the rest of this comment and returning to this opening paragraph if/when you think you might understand it.)
No you don't:
You were just confused about it.
Exactly. Hence we need to do something about the thorny problems that has created. First comes the technical problem. While In stark contrast And that leads to the consequent problem. What do we do about this problem? A lot of us are hoping we can clean up aspects of Raku like this that create huge problems with very little benefit. The globality and dynamicism of But those who don't really understand the problem (and I take it that that includes you) may only see a benefit we want to remove while not seeing the huge problems that come with the benefit. So our only option is to try develop more understanding in the user base in the hope we can build consensus supporting consideration of elimination of the problematic feature.
|
@raiph It's an undeniable fact that if you remove the parens I referred to, you don't get the desired two-column "word, last-letter" output: admin@mbp ~ % echo 'cat dog horse' | raku -ne 'my @a = .words; for @a { say "$_ ", ($/ given .match(/ .$/))};'
cat 「t」
dog 「g」
horse 「e」
admin@mbp ~ % echo 'cat dog horse' | raku -ne 'my @a = .words; for @a { say "$_ ", $/ given .match(/ .$/) };'
t 「t」
g 「g」
e 「e」 Most languages start from the premise that if you enter minimal code, you accept the defaults the language gives you (including operator precedence). By adding extra named-arguments, you can customize your output. Finally, if default operator precedence is a problem, you use the ultimate "nail clipping" solution of parenthesizing everything. The concept of "magic" upsets the apple-cart: trivial things become difficult, and vice versa. Your concept of "magic" will most certainly not match my concept of magic. Plus, if you design you language "magic" to appeal to a hypothetical population (lets say...Perl programmers), then you end up confusing/frustrating those outside your putative audience. For some (part-time, scientific, data-sciency) programmers, this is a signal that it's time to walk away. |
Sure. (I always run code that anyone shares as the very first thing I do as part of trying to help them.)
Sure. Thus, for example, when you wrote
Do you mean it can make code easier to understand if you break data out and store it in appropriately named variables? Assuming so, then yes, that sometimes makes sense.
While using nail clippings may be what you prefer, I generally prefer to minimize parens that add to noise and instead look for a different solution, and I know a lot of Rakoons have the same preference. That's why, when you wrote "I have to parens the match clause to get it to work" I thought you would be pleased to read that you don't have to. But now I'm confused. Would you prefer not to read comments suggesting how you can solve problems in simple ways after you've suggested you have to do it an ugly or complicated way?
Yes.
Yes. And that makes matters worse.
Yes. And that makes matters worse still.
Yes. But imo, even more important than all the above negatives that can be associated with "magic", is when some magic provides tiny benefits and massive problems. At that point it becomes almost absurd for a PL to continue to support those particular forms of magic. And at a major PL version break like |
@patrickbkr wrote:
Could you provide version numbers? And when/which-version you saw the change? Thx. |
@raiph wrote:
~ % raku -e 'for lines() { put join "\t", $_, $/, $0 if m/ ^ aa(.) / };' /usr/share/dict/words
aal aal l
aalii aal l
aam aam m
aardvark aar r
aardwolf aar r |
The piece of my comment you quoted has no relevance to the code you showed. I suspect you have misunderstood what I'm asking you to think about. This issue discusses some technical problems that have long threatened to destroy the entire language. Do you recognize that? This issue discusses options we have for solving them, options which would break backwards compatibility. Do you recognize that we need to very seriously consider them? Almost none of what has been discussed in this thread, problems or solutions, has been about altering the behavior of the code you showed. The fact you shared that code suggests to me that you don't understand what is being discussed. Worse, you shared it without discussing specifically how it relates to specific points made in this issue. This is part of the reason I asked you to be more specific about what you meant when you wrote:
What do you mean? "Quote-unquote magic and magical actions should be reduced to a minimum." is fair enough provided you explain what you define as "magic". Until you do that it's ultimately unhelpful at best, confusing at worst. Furthermore, comments about "magic" are typically used to argue against features such as To help show that you understand (or don't) particular aspects of what is being discussed in this issue, please comment on your specific position relative to each of the following numbered aspects, and explain what impact you think each aspect would have on the code you just shared, if you think it would in fact have an impact:
TIA for following up. We need to make progress on dealing with thread (un)safety in Raku, and we need to take the opportunity of That doesn't mean we break backwards compatibility willy nilly. But it also means we're not going to maintain backwards compatibility just because some users don't yet understand what the problems are. We need to talk this stuff through and get on the same page, or move ahead to secure the language's future, and this includes things as fundamental as thread safety of basic features. |
The following code produces consistent results:
However, if we put a
.race
or a.hyper
in the mix (as in("aaa" .. "zzz").race.map( {
), the results become random with only occasionally producing the correct result.This can be fixed by adding a
my $/
inside the code block of the.map
. Or by changing the block to a propersub
(("aaa" .. "zzz").map( sub ($_) {
).So clearly this problem is caused by the block in the
map
not having its own lexical$/
, so thatG.parse
is setting the$/
that is implicitly defined outside the block, insidesub summarize
.In 6.e currently Grammar.parse (and friends) will not set
$/
, and then the above code will work as it should. However, that is a breaking change that we may not want to keep. On the other hand we may want to keep this change to reduce "action at a distance" more generally.The text was updated successfully, but these errors were encountered: