Why isn't it possible to recover/resume after an Exception? I know that Exceptions as a means of flow-control is generally frowned upon.

But in my opinion, Exceptions have little value short of the flow-control aspect - after all, if you didn't want the program to continue, you could just output an error-message an terminate the program.

Exceptions, on the other hand, provide a means of reporting errors "locally", allowing a service/component to fail, and a consumer to handle the failure - and regardless of how you look at that, it is a means of controlling the flow of the program.

So here's my question - over the years, I have frequently wondered, why isn't it possible to resume execution after an exception is thrown?

Now, you wouldn't want to allow any consumer to resume after an Exception thrown by any other component, as that component was probably not designed to resume after a throw-statement, which would lead to unpredictable results.

So let's say there's a supertype of Exception called Interrupt, that allows this behavior. An Interrupt would behave just like an Exception in every respect, except that by throwing an Interrupt, you indicate that the component is ready and able to resume execution after the throw-statement, and that the stack needs to be preserved either until (A) the Interrupt has been handled, or (B) the program exits with an error-message.

Let's say we add a new "resume" statement to the language, to be used inside a traditional "catch" block - if you catch an Interrupt, and issue a "resume" statement, control would return to the point from where the Interrupt was originally thrown, and execution would continue from the next statement.

I've presented this idea in other circles, and I'm met with a lot of resistance, but no clear argument as to why this is not a good idea.

To me, it seems like a natural extension of the idea of exceptions - there are plenty of cases where this could be useful, for example while enumerating a sequence, e.g. in a function that "yields" one result at a time; an unexpected condition could occur while producing one of these results, and you may want the calling code to decide whether or not it makes sense to continue producing more results.

An exception does not allow for that.

Let's say this function throws an interrupt instead - if so, the calling code now has a chance to look at that and decide whether to resume execution (as if the exception never occurred) and produce more results, perhaps log the condition and then resume, or perhaps throw an exception, or perhaps re-throw the interrupt in case it can be handled up-stream.

I'm sure I'm not the first person to have this idea, but I would like to understand why this isn't feasible or why it's not a good idea.

(PS: I'm a programmer, not a scientist, so go easy on me.)
"Resumable exceptions" are indeed a well-known idea in some programming language circles. In particular, Common Lisp has had resumable exceptions for a long time (so they are really not confined to research language; Common Lisp could be considered "mainstream", it has been widely available and relatively widely used for a long time).

For a discussion of resumable (or "restartable") exceptions, see for example this blog post by Manuel Simoni, which links to this dylan mailing-list post by Chris Double and the following Lambda-the-Ultimate discussion: Common Lisp exception handling.

You made the insightful guess, that resumable exceptions are not a good idea in all contexts. They can make resource handling harder and more generally anything related to side-effects must be considered very carefully when you add new control-flow entry points to a block of code.

It is possible for the exception raising code to decide on whether it should be resumable or not. This is natural if you see resumable exceptions as a combination of usual exceptions and continuations: you raise an exception and, in the attached data, decide whether or not to include a continuation to the rest of the computation; the handler then decide whether or not to invoke that continuation, that is "resume" the exception.

Effect handlers (try..catch block, using-like resource handler assuming lexical lifetime) can get confused if you re-enter the computation. Combining handlers and rich control flow is difficult and gave way to a lot of different approaches in the Lisp/Scheme communities (keywords: dynamic-wind, unwind-protect). You are certainly adding non-trivial complexity to the language.

I speak of "effect handlers" because, once you have such rich forms of control flow, you get very close to the ability of presenting arbitrary effects (exceptions, backtracking, but also logging, mutable state and input/output) on top of it. See for example Andrej Bauer and Matija Pretnar's work on Eff a language based on such "effect handlers".