feat: deep handler semantics for effect rotation#2
Merged
Merged
Conversation
Member
|
Awesome, thanks for detecting this and fixing. Do you mind to include a test that demonstrates this feature? |
0e86a1a to
f945464
Compare
Problem: when scope.run installs scoped handlers, effects not matching the scope rotate outward to the enclosing handler. If the outer handler returns an effectful resume (a computation), those resume effects were processed by the outer interpret loop — never re-entering the inner scope. Scoped handlers were invisible to effects originating from outer handler resumes. Example: inner scope handles effect A. Computation sends effect B (not in inner scope). B rotates to outer handler. Outer handler for B returns resume = send "A" null. Previously, A was processed by the outer interpret (unhandled error). Now, A passes through the inner scope's handlers first. Solution: three changes in the trampoline. 1. effectRotate/effectRotateSlow tag rotation continuations with __rawResume = true on the queue. 2. interpret checks __rawResume: if true, uses resumeWithQueue (passes raw resume to rotation continuation) instead of resumeCompOrValue (which would eagerly splice resume effects into the interpret loop). 3. The rotation continuation uses resumeCompOrValue to route the raw resume through effectRotate, where inner handlers get first opportunity to handle the effects. Non-matching effects re-rotate outward as before. This implements deep handler semantics as described in the algebraic effects literature (Plotkin & Pretnar 2013, Koka, Eff, OCaml 5). Continuations now capture the full handler stack — when a handler resumes, the continuation runs with all handler layers still active. Backwards compatible: all existing tests pass unchanged. 3 new scope tests validate deep handler behavior.
Collaborator
Author
|
sniped |
Collaborator
Author
|
actually it looks like you merged the pre-fix in |
Member
|
Ok will merge from latest commit 23965f1 you pushed, is that the right commit ? |
Collaborator
Author
|
yes |
Member
|
done. |
Collaborator
Author
|
FYI: It was only styling/rebasing to try and remove your 'den version' from my tree. |
Member
|
oh, hahaha. I was keeping that |
Collaborator
Author
|
I assume you'll just drop/re-order it when you need to. :) |
Member
|
yes, I do when our PRs get upstreamed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem: when scope.run installs scoped handlers (e.g. a value
provider), effects not matching the scope rotate outward to the
enclosing handler. If the outer handler's resume is an effectful
computation, those resume effects were processed by the outer
interpret loop — never re-entering the inner scope. This meant
scoped handlers were invisible to effects originating from outer
handler resumes.
Example: inner scope handles effect A. Computation sends effect B
(not in inner scope). B rotates to outer handler. Outer handler
for B returns
resume = send "A" null. Previously, A was processedby the outer interpret (unhandled → error). Now, A passes through
the inner scope's handlers first.
Solution: three changes in the trampoline.
effectRotate/effectRotateSlow tag rotation continuations with
__rawResume = true on the queue.
interpret checks __rawResume: if true, uses resumeWithQueue
(passes raw resume to rotation continuation) instead of
resumeCompOrValue (which would eagerly splice resume effects
into the interpret loop).
The rotation continuation uses resumeCompOrValue to route the
raw resume through effectRotate, where inner handlers get
first opportunity to handle the effects. Non-matching effects
re-rotate outward as before.
This implements deep handler semantics as described in the algebraic
effects literature (Plotkin & Pretnar 2013, Koka, Eff, OCaml 5).
Continuations now capture the full handler stack — when a handler
resumes, the continuation runs with ALL handler layers still active.
Backwards compatible: all 1636 existing tests pass unchanged.
Plain value resumes (the common case) are unaffected.
3 new scope tests validate deep handler behavior.