Implement exceptions for effect handlers using Callback.register_exception#11423
Implement exceptions for effect handlers using Callback.register_exception#11423Octachron merged 14 commits intoocaml:trunkfrom
Callback.register_exception#11423Conversation
|
Personally I find |
| (code_t)raise_unhandled_effect_code; | ||
| Closinfo_val(raise_unhandled_effect_closure) = Make_closinfo(0, 2); | ||
| raise_unhandled_effect = raise_unhandled_effect_closure; | ||
| caml_register_generational_global_root(&raise_unhandled_effect); |
There was a problem hiding this comment.
Can you explain to a newcomer why this black magic around raise_unhandled_effect is necessary? (The explanation could be added as a comment in the code.)
I'm not sure what parts of the bytecode are allowed to directly raise exceptions (apparently caml_raise_continuation_already_taken() below is fine, and there are existing examples with caml_raise_stack_overflow for example), and which are not / why. Here what's going on seems very complex compared to the semantics we want, or even to the handling of unhandled exceptions in
Lines 972 to 977 in 37df934
There was a problem hiding this comment.
Essentially, raise_unhandled_effect is necessary when we do not find a matching effect handler in our stack of handlers. The search had proceeded by checking one effect handler on the stack at a time. When we reach the bottom of the stack of effect handlers and find that there is no parent stack, we have to get the control back to the point of perform (do_resume) and then raise the exception. raise_unhandled_effect is the closure that raises the exception immediately after the control returns to the point of perform.
I'll write a clarifying comment in the code.
|
I'm not sure that an example can actually be concocted which would trigger this, but it reads slightly strangely that the |
|
Typo in the Git commit message of 37df934: " |
|
I won't review the implementation until we all agree on the interface and the documentation. +1 for @dbuenzli's suggestions, especially the suggestion to shorten |
| !4] | ||
| !5] | ||
| Unhandled | ||
| Stdlib.Effect.Unhandled_effect(_) |
There was a problem hiding this comment.
This (_) is unfortunate and reduces the usefulness of including the effect in the exception. It would be 100% better to also print the name of the effect ! For this we'll need some ad-hoc code in the exception printer(s), I'm afraid.
There was a problem hiding this comment.
Good point. I see that there is similar code in printexc.ml.
|
As the person who originally requested the renaming of |
It would be odd to not find the exception when using Lines 669 to 674 in 37df934 If we think that there is a possibility of these exceptions not being registered (with stdlib replacements), then it may be easiest to replace this assertion with a check that |
|
@dra27: If I am not mistaken, the exceptions can only be raised by OCaml code that links Effect, thus it seems fine to do the initialization only when linking the |
That's unfortunately not the case. Defining a new effect only needs to reference the interface, not the implementation, and the same applies for performing an effect ( Overall, I consider that if there is any amount of initialisation code in a module, it should be compiled with
|
This seems like an adversarial case. Is it possible to write programs which will perform effects without linking to the Effect module? In other words, is this problem encountered by programmers who are not actively trying to use effects without linking to the effect module. We should certainly address this issue if the programmer is not trying to do something clever but encounters this issue.
What do you think about the following options?
|
|
Again we're discussing implementation before we've agreed on the API...
That's what otherlibs/unix does for the Unix_error exception. A fatal error is also possible, see Lines 204 to 218 in ef376dd Please forget about the implementation and discuss the API (incl. documentation comments). |
Do we agree that changing the exception There are a few (potentially non-controversial) tasks left for improving the PR:
|
|
I think that renaming the exception to Thinking a bit about the If the later option is a possibility, it might make sense to expose the effect module as a library. This has the advantage of making it clearer that the module is an experimental API that does not come with the backward-compatibility guarantee of the standard library. Moreover, integrating a library in the standard library is both simpler and faster than removing a module from the standard library. |
It also entails quite a bit of bureaucracy. In general the whole process around the stdlib remains a bit unrealistic in my opinion, especially for something like the 5.0 transition. You can think as much as you want about design, design errors still occur and most of the time you only get to know about them once it's in the wild and the stuff gets used in anger. The current process entails that if a design error gets made it is cast in stone for the next 20 years which is not so great. I think the stdlib should have a notion of Personally I would even flag all of the entry points introduced specifically for |
xavierleroy
left a comment
There was a problem hiding this comment.
Let's try to agree on the API, shall we?
|
Thanks @jonludlam for the help. With the latest commits, the outstanding tasks mentioned in #11423 (comment) are complete. |
|
It seems there are a few additional details of the API that were implicitly agreed upon: (Or, at very least, I did not see any objections.)
The reason I brought up (2) is that it seems to be the only reason to favor exception registration over built-in exceptions. As for (1), if the current PR is tracking this API design, perhaps Personally, I also like @dbuenzli's suggestion to mark new things experimental. Is this part of the API discussion? If so, I support marking both |
Just to clarify: I do not consider this necessary at all. First, anyone can build on top of the standard library, there is seldom a need to replace it entirely. Second, a stdlib replacement can register exceptions itself, like the stdlib would do. Built-in exceptions and |
xavierleroy
left a comment
There was a problem hiding this comment.
I read stdlib/effect.mli again, and the API and doc strings look just fine to me. Thanks! @favonia @dbuenzli @fpottier and others: if you have reservations about the current API and documentation, now is the time to tell.
I started looking at the implementation and I have a number of suggestions to make, probably tomorrow.
dbuenzli
left a comment
There was a problem hiding this comment.
Docs and names look good. Thank @kayceesrk.
stdlib/printexc.ml
Outdated
| | [] -> None in | ||
| conv (Atomic.get printers) | ||
|
|
||
| let walk_sum_type x = |
There was a problem hiding this comment.
(This is about the implementation.) I personally prefer the name extensible_variant over sum_type because the printer here does not work for all variant types (and in OCaml documentation the term "sum types" is not used). This comment also applies to the function string_of_sum_type a few lines below.
There was a problem hiding this comment.
I noticed that in effects.etex, Unhandled is qualified (as Effect.Unhandled) but Continuation_already_taken is not, even though both are defined at the same place now. Otherwise, everything else looks great. @kayceesrk Thank you. 🙂
I needed to introduce the (Apologies for the slow response on this thread. It is the first week of teaching in the new semester for me.) |
It seemed sensible not to duplicate the logic to turn sum types into strings that is currently in use for exceptions. However, since it's not exposed in Printexc, and the exception being matched is in effect.ml, which depends on Printexc, I've opted for matching the string "Stdlib.Effect.Unhandled" and assuming the single argument is the effect.
In this case, produce a somewhat clean fatal error if these exceptions are raised.
The documentation was already using "resumed" rather than taken which is a good sign that "Continuation_already_resumed" is clearer.
2f22a81 to
b65695a
Compare
|
I have updated and rebased the PR and added @xavierleroy's commit and mine to the branch. @favonia: After this PR neither the |
I think the difference between "a built-in exception" and "an exception defined in the standard library" is very small. It's only meaningful to people who build compilers or alternative standard libraries. Therefore, I still feel it is better to include all (public) exceptions defined in the standard library. That said, I do not have a strong opinion because the output will be clear either way. I see that there are some unaddressed comments about the documentation. Other than those, I am very satisfied with the current PR and hope it can be shipped with the next alpha or the beta! 😃 |
|
The PR as it stands looks good to me. The only point of contention is the choice between the strings I realised I can't approve the PR since I'd opened it! @Octachron feel free to merge when you are happy. |
xavierleroy
left a comment
There was a problem hiding this comment.
Approving on behalf of @kayceesrk and @Octachron , who both wrote the code and reviewed the other's code.
|
There's a bootstrap in the middle, so I'd rather let @Octachron handle the final merge. |
|
Merged and cherry-picked by hand to 5.0 . |
|
The |
This PR removes the pre-defined exceptions
UnhandledandContinuation_already_takenused by effect handlers. Instead, we now declare these effects in the Effect module, and register them usingCallback.register_exceptionto enable the runtime to raise these exceptions.This PR also replaces the exception
UnhandledwithUnhandled_effect : 'a Effect.t -> exn, which carries the unhandled effect.The PR supersedes #11419 and fixes #11185.