On Nov 5, 2019, at 21:48, Random832
As a side note, I have, occasionally, wanted to be able to resume a function after handling an exception (the use case was to turn a synchronous outer function calling an asynchronous callback into an asynchronous function), which - needless to say - is impossible in CPython. In a hypothetical implementation that would allow such a thing, having the raise return a value in such a scenario might not be unreasonable.
That’s not just a CPython limitation, it’s a limitation of the defined Python semantics. Python unwinds the stack before calling exception handlers. For function exits that’s not a big deal (you’re not allowed to rely on garbage being cleaned up deterministically, and you can’t detect that something is garbage until it’s cleaned up), but it would change the semantics—and break most non-trivial uses—of finally clauses and with statements. You could maybe redesign the context manager protocol so they understand and participate in resume logic somehow (separate __initial_exit__ and __final_exit__?). But what do you do with finally clauses? Another option is to do what Dylan does. I don’t actually remember the details, but it’s something like this: the way you resume an exception is to resume-raise a new exception back at the point it was raised. Then, the interpreter can stack up exception handlers and unwind chunks in a way that makes sense (it needs to rearrange that stack on the fly, but only in a simple way that Python already requires even for simple generators). Code that doesn’t ever resume works the same as always. Code that does has to be written differently, and has to be explicit about the order of unwinds and handlers, but can do so just by using the normal Python indentation (the try statement that handles the resume-raise is either inside or outside the with or try/finally or function). This also makes it very easy to turn a resume into a retry (by just putting a while outside the inner try). And anything retryable also has the advantage that pdb can optionally keep the whole stack around until you decide not to retry, which can be helpful for grubbing around in the debugger, but then clean it all up properly as soon as you do decide. IIRC, Common Lisp does something pretty similar to Dylan, except that instead of resumes being exceptions they’re a separate thing that just works nearly identical to exceptions. That might be more readable, or maybe even easier to implement, even if it adds more concepts to the language. I believe there’s also a rejected C++ proposal for resumable exceptions, and a g++ fork that implements it, from way back before C++11. Since C++ is heavily designed around RAII (everything is a context manager), they must have come up with a solution. On the other hand, there’s probably a reason it was rejected—although that reason might just be “We had an extensive discussion on signal-like vs. termination semantics when first doing ANSI C++, and this is close enough to signal-like that without any new use case we’re not going to reopen that discussion.” I don’t know of any other post-70s languages that do resumable exception handling that aren’t continuation-based, but they probably do exist. But for a new Python-like language with resumable exceptions, I think you’d want to do it with continuations, because that’s the easy way. Adding a callcc and first-class continuation objects would probably be easier if you first removed try and with entirely, then translate everything to cc semantics, then redesign them on top of cc. Everything should be pretty simple. And you can even experiment with writing variations of the logic in-language as functions before writing the Python syntactic sugar. At that point, you could even remove the first-class continuations if you want, and translate the language back to non-continuation-based semantics. It might be easier to optimize things that way, and it would probably make it a lot easier to write Python implementations similar to Jython and Iron that rely as much as possible on the semantics of an underlying high-level language. Or maybe don’t bother with any new syntax. Once you’ve got existing Python semantics plus callcc, just leave syntactic exceptions as-is with no resume, and make resumable (and retryable) exceptions a library thing that you can use in the cases where they‘re needed; those cases are probably rare enough that it doesn’t matter if they’re not pretty. Also, I think you’d find that almost everywhere you think you want a resumable exception, you can actually do it more simply by either using continuations directly, or using a different abstraction on top of them. That certainly seems true in your use case, where your goal (making a sync function async) has nothing to do with exceptions, and the only reason you thought of resumable exceptions was, effectively, as a way to clumsily simulate callcc.