
Let's get to the fundamental problem with this. It is DWIM magic, and you haven't (as far as I have seen) yet specified how we are supposed to use it or predict how it is supposed to work. Here is your syntax again:
def a_potentially_recursive_function(some, args) with ExceptionWeCareAbout: some.user_code() code_we_assume_is_safe() if args.something and some_condition: raise ExceptionWeCareAbout # Line (A)
How does the compiler know that *only* ExceptionWeCareAbout originating in Line (A) should be re-raised, and any other location turned into RuntimeError?
Same way Rust decides whether to propagate or unwrap a Result: you *must* tell the compiler.
How do I tell the compiler? There is no sign in your syntax that Line (A) is special or different from the rest of the lines in the function.
What if I factor out those last two lines and make it:
def a_potentially_recursive_function(some, args) with ExceptionWeCareAbout: some.user_code() code_we_assume_is_safe() check_condition_or_raise(args.something, some_condition)
How does the compiler decide to re-raise exceptions originating in the last line but not the first two?
In this case, it explicitly doesn't.
So by refactoring a conditional raise (if condition: raise) into a function, I completely change the meaning of the code? That's just great. Not.
You explicitly told it the last line doesn't raise any exceptions that contribute to your API's exception surface.
I did? I wasn't aware that I told the interpeter *explicitly* anything about the last line. How does this work? That's what I'm trying to understand about your proposal: I write a function, and stick "with ExceptionName" into the function declaration line: def function(some, args) with ExceptionWeCareAbout: and then write a block of code under that declaration, and *somehow* in some completely unspecified way the compiler Does What I Mean by deciding that *some* of those lines which raise ExceptionWeCareAbout it should let the exception through while *other* lines that raise the same exception should be guarded against ExceptionWeCareAbout and have them raise RuntimeError instead. And I have no idea how it decides which lines are guarded and which are not. You tell me that I explicitly instructed the compiler which lines should be guarded, but I don't know how I did it.
You *must* use try: check_condition_or_raise(args.something, some_condition) except ExceptionWeCareAbout: raise
But that's not what the pre-refactoring code had. All I did was move: # The original version, before refactoring. if args.something and some_condition: raise ExceptionWeCareAbout into a named function and call that. In the original code, I didn't have to explicitly catch the exception and then immediately re-raise it: # This was never used. try: if args.something and some_condition: raise ExceptionWeCareAbout except ExceptionWeCareAbout: raise But now you are telling me that if I move the `if... raise` lines into a function I have to also wrap it in a try...except, catch the exception, and immediately and unconditionally re-raise it. This behaviour may be clear to *you* but I cannot read your mind. Unless you actually tell me how the compiler knows which part of the block need to be guarded and which parts don't, I have no clue how this is happening except "the compiler magically infers what I want it to do". -- Steve