-
Notifications
You must be signed in to change notification settings - Fork 16
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
LEAVE phaser with return #417
Comments
With MoarVM/MoarVM#1785 and MoarVM/MoarVM#1782 merged current rakudo prints:
I especially wonder about 2. and 4. It's comprehensible what's happening and why, but feels like a WAT. Maybe it's simply a DIHWIDT, similar to what
|
In my opinion a sub a { FIRST return 42 }; dd a; # 42 So in my book, the return values would be: 5, 1, 5, 1 |
Or we should disallow |
There is a problem with cases 2 and 4. Both has to be 7. And here is why. Return from a block applies to the closure where the block belongs to. This is why
Better not. |
Ok, thinking about this more: I change my answers to 5, 5, 5, 1. And here's why: In case 2, since I don't see an issue with 4. The same logic as 2. applies here. |
@lizmat you're wrong. Letting a block return from sub foo(&c) {
my \rc = &c();
say "foo rc=", rc.raku; # no output
rc
}
sub bar() {
foo { return 1 };
return 2
}
say bar; # 1 I will remain in the position that a block must return from its closure. |
I think it will help to explain what Rakudo currently does technically. Looking at the 2nd example:
I think the behavior is consistent in that once I understood that @vrurg I believe the above behavior is interesting, because your requirement
is upheld. (I strongly support this requirement.) But the Just to state a possibility of how we could adapt the above logic:
This would have the consequence that the results would be:
I'm undecided if I like this or not. It limits action at a distance: The |
All of this explanation that is needed to explain behaviour, is convincing me that a But we could at run-time by codegenning a LEAVE {
say "bye";
return 42;
} would be codegenned as: LEAVE {
CONTROL {
die "return within a LEAVE phaser" if $_ ~~ CX::Return
}
say "bye";
return 42;
} Of course, if the Since phasers cannot be inlined anyway, I don't think this will affect performance of phasers much. |
To actually not break any code out there that is depending in old " |
What would it change in The noticeable change is that immediate termination of So, I currently see no cardinal changes that might break
While not being fully against this prohibition, I'd not appreciate it. Besides, consider that
Again, hope this won't be needed. But technically, an explicit
Both being True means this
Even if we need it, I wouldn't be happy about extra implicit code injection. A VM can be more efficient in implementing this. And more universal. But either way, we haven't considered all possibilities of optimizing the case. The fact that any return within frames, affected by unwind, is not going to be used makes a lot of things easier, as I see it. All we need is to make sure all related |
Adding case 5.
This is in some sense the inverse of case 2. (I don't see how we can distinguish the two cases, i.e. to make case 2. give Current Rakudo (with the two PRs applied) prints |
It makes no difference with 2. More interesting the case of
But the second option loses for two reasons. First, the |
@vrurg I try to explain some more why I think case 2. vs case 5. is difficult. case 2.
case 5.
Let's consider the point during execution marked with "collision". In case 2 that's In case 5 that's When working with the information given in the previous two paragraphs, the outcome is either |
Hope, we're not discussing different things here. Also hope that you're not looking at the problem from the perspective of implementation. Separate the concerns. Separate the sources of Think of it from the day-to-day development. The author of The author of That's why I say 2 and 5 are the same. Because all what matters here is |
Maybe "inspired by"... Can't prevent that.
I see what angle you are coming from and I agree. I just have difficulty mapping what that means technically. After some thinking: Always let the return win, that wants to unwind further down the stack. Could that be it? |
I would tentatively agree. Not sure if we miss no nuance here. |
Calling &return triggers a stack unwind which can cause code (e.g. in `LEAVE` phasers) to run. That code can also call `&return`. In such situations one usually wants the return to win that unwinds more of the stack. This is the behavior implemented by this change. Previously the later return always won. Related to Raku/problem-solving#417
Discussed in Raku/problem-solving#417 Depends on Raku/problem-solving#417
I have added an implementation of the "deeper unwind wins" approach in MoarVM/MoarVM#1786. Spec tests pass including those in Raku/roast#851 |
Calling &return triggers a stack unwind which can cause code (e.g. in `LEAVE` phasers) to run. That code can also call `&return`. In such situations one usually wants the return to win that unwinds more of the stack. This is the behavior implemented by this change. Previously the later return always won. Related to Raku/problem-solving#417
We do. Always picking the return that unwinds deeper basically disables |
Another idea:
I think the semantics are correct and the approach to have frames in a "returning" state feels like it matches the core of the issue. I see issues though:
|
Yet another idea: |
In general, if a &return was called in a routine, we don't want any other code in exit handlers to interfere with that return from returning. It's possible for exit handlers to escape normal control flow (e.g. via a &return). In that case instead of reaching the handler the unwind targets, we would return to the frame's return_address. To make things right, we tune the unwind target frame's return address to point to the handler. Also set that frames RETURNING flag and preserve the return value, getlexpayload can then pick it up again. Related to Raku/problem-solving#417
Discussed in Raku/problem-solving#417 Depends on these three PRs: - MoarVM/MoarVM#1782 - MoarVM/MoarVM#1785 - MoarVM/MoarVM#1788
I have implemented the last idea in MoarVM/MoarVM#1788, it turned out rather nice. I have also added another test to Raku/roast#851. It passes all those tests. I haven't done a spec test (need to add a JIT implementation to MoarVM/MoarVM#1788 first), but this looks very promising. |
To summarize: there's an implementation now that implements: "first |
|
The PR is still marked as draft. Maybe it shouldn't? :-) |
I can change that. But: We should decide if those semantics are what we actually want. From the above discussion I don't have the impression that we have reached a consensus yet.
So, can we first decide that we want those semantics, then go forward with the implementation. Right?
…On February 25, 2024 3:13:45 PM GMT+01:00, Elizabeth Mattijsen ***@***.***> wrote:
The PR is still marked as draft. Maybe it shouldn't? :-)
--
Reply to this email directly or view it on GitHub:
#417 (comment)
You are receiving this because you authored the thread.
Message ID: ***@***.***>
|
+1 for first return wins. We already have a similar behaviour regarding
|
I know I'm late to the party, but anyway. S06 says "The It says this about ENTER/LEAVE/KEEP/UNDO/etc. phasers: "These phasers supply code that is to be conditionally executed before or after the subroutine's The first quoted paragraph makes me inclined to suggest "lastest wins" semantics for return. After all return throws a control exception and with exceptions in general it's that any exception occurring after the original exception (e.g. in the exception handler) will become the dominant one that get's passed up to handlers. This matches what I would expect. But then S06 is quite explicit about phasers, especially LEAVE being about their side effects. So now I'm a bit torn. |
Could also be interpreted as "don't allow |
@niner
From all I've seen in this discussion I'm pretty confident to say that lastest win semantics are effectively non-composable and the only safe move is this to never use return in LEAVE. We'd be better off prohibiting return in LEAVE.
Reading through those snippets you provided, I suspect at the time people didn't realize that combining LEAVE and return can happen and what issues that poses. They provide a good implementers POV though.
…On February 25, 2024 8:15:12 PM GMT+01:00, niner ***@***.***> wrote:
I know I'm late to the party, but anyway.
S06 says "The C<return> function notionally throws a control exception that is caught by the current lexically enclosing C<Routine> to force a return through the control logic code of any intermediate block constructs. (That is, it must unwind the stack of dynamic scopes to the proper lexical scope belonging to this routine.)"
It says this about ENTER/LEAVE/KEEP/UNDO/etc. phasers: "These phasers supply code that is to be conditionally executed before or after the subroutine's C<do> block (only if used at the outermost level within the subroutine; technically, these are added to the block traits on the C<do> block, not the subroutine object). These phasers are generally used only for their side effects, since **most return values will be ignored.** (Phasers that run before normal execution may be used for their values, however.)"
The first quoted paragraph makes me inclined to suggest "lastest wins" semantics for return. After all return throws a control exception and with exceptions in general it's that any exception occurring after the original exception (e.g. in the exception handler) will become the dominant one that get's passed up to handlers. This matches what I would expect.
But then S06 is quite explicit about phasers, especially LEAVE being about their side effects. So now I'm a bit torn.
--
Reply to this email directly or view it on GitHub:
#417 (comment)
You are receiving this because you authored the thread.
Message ID: ***@***.***>
|
This discussion seems to have stalled. Let's summarize the options again:
@lizmat, @niner: I gave wrong information a few posts earlier, I mixed things up. There is no implementation of first return wins. I'm in favor of 1. Everyone in this discussion (@niner @vrurg @lizmat): Can you give a quick info where each of you stands? I think it's possible there isn't even a disagreement here. |
I still stand for 1, no changes here. |
@niner, @lizmat Ping! Can you restate your positions? See #417 (comment) |
I can live with 1. However, that is different from the behavior of |
Discussed in Raku/problem-solving#417 Depends on these three PRs: - MoarVM/MoarVM#1782 - MoarVM/MoarVM#1785 - MoarVM/MoarVM#1788
Re-reading this thing I'm with @vrurg that the correct result would e 5, 7, 5, 7. According to the design docs I quoted a
then it's more obvious that because of the So yes, a |
This comment was marked as resolved.
This comment was marked as resolved.
@raiph Thanks for your feedback!
Correct, I was talking about option 4.
I believe we agree here. |
We've finally reached consensus. Accordingly I have just removed draft marker from the PR implementing the desired behavior (MoarVM#1788). For completeness sake, there are four PRs related to this issue:
I'd like to get all four of them merged in the not too far future. |
Discussed in Raku/problem-solving#417 Depends on these three PRs: - MoarVM/MoarVM#1782 - MoarVM/MoarVM#1785 - MoarVM/MoarVM#1788
Calling &return triggers a stack unwind which can cause code (e.g. in `LEAVE` phasers) to run. That code can also call `&return`. In such situations one usually wants the return to win that unwinds more of the stack. This is the behavior implemented by this change. Previously the later return always won. Related to Raku/problem-solving#417
Calling &return triggers a stack unwind which can cause code (e.g. in `LEAVE` phasers) to run. That code can also call `&return`. The returns conflict, if the later return unwinds over the return continuation marker of the former return in the call stack, i.e. the `LEAVE` frame. In such situations one wants the return to win that unwinds more of the stack. This is the behavior implemented by this change. Previously the later return always won. This implements the behavior discussed in Raku/problem-solving#417.
I believe the interactions with other exception types need some thought as well. I guess Intuitively I'd say that if a non-return exception is in progress, a return in LEAVE should never take over. So is the distinction |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
Have you read the original design doc verbiage related to this? Excerpts from the last part of the Phasers section in S04:
|
Idea: Let's distinguish Given the word "conflict" means: An exception thrown within a phaser during an unwind (a Then I propose the following rules:
|
I imagine your initial goal is to decide what to do for a Then, once you're confident there's core dev consensus for the behavior of a
Sounds sensible to me.
The keywords listed in the doc as raising control exceptions are To test my understanding of your proposal, I think the list of keywords whose corresponding control exceptions can't "conflict" as you've defined are something like return (!), redo, next, last, done, emit, proceed and succeed. Right? (When I say "can't" I mean I'm thinking things like they either logically can't, or can only do so given absurdist DIHWIDT coding that I'm not even sure is possible. I've not really thought through And presumably the ones that could "conflict" (though again, perhaps DIHWIDT applies) are at least I also wonder if the following should be considered before deciding what to do about these "conflicts":
I mention that because I'm thinking that if that information has been made available to a (Though perhaps one would just write separate |
A simple example of a conflicting
Actually I know little about phasers other than One more unsolved issue: There are exceptions that can resume, some always do, e.g. |
What should happen when a LEAVE phaser contains a
return
statement?Given these four snippets, what should each of those print?
The text was updated successfully, but these errors were encountered: