On Sat, Sep 26, 2020 at 09:59:27AM +0200, Sascha Schlemmer via Python-ideas
wrote:
> I think a clear distinction has to be made between errors that belong
> to the api surface of a function e.g. some `ValueError` or
> `ZeroDivisionError` and *exceptional* errors e,g.`KeyboardInterrupt`
> or `DatabaseExplodedError`.
One problem is that while we can make a distinction between "expected"
errors and "unexpected" errors, it's subjective and anything but clear.
Whether you handle it, or just let it go, depends in part on the
software you are writing.
> For the first case I think a better way to model a function, that has
> some *expected* failure mode is to try and write a *total* function
> that doesn’t use exceptions at all. Let me show what I mean:
>
> ```
> def div(a: int, b: int) -> float:
> return a / b
> ```
>
> This function tries to divide two integers, trouble is that `div` is
> not defined for every input we may pass it, i.e. passing `b=0` will
> lead to `ZeroDivisionError` being raised. In other words `div` is a
> *partial* function because it is not defined for every member of its
> domain (type of its arguments).
> There are two ways to amend this issue and make `div` a *total*
> function that is honest about its domain and co-domain (types of input
> arguments and return type):
You're making a mighty effort to bring some functional programming
concepts to Python, but it doesn't really work. For example:
> 1. Extending the type of the functions co-domain (return type) and
> making the error case explicit
[...]
> ```
> def div(a: int, b: int) -> Maybe[float]:
> try:
> return Some(a / b)
> except ZeroDivisionError:
> return Nothing()
> ```
Alas, this still doesn't work because type annotations are advisory and
not mandatory. Regardless of the type hints given, I can still call
`div(1, "surprise")` and get a TypeError.
Worse, because types in Python use subtyping, I can pass an int that
does something you don't expect:
class MyWeirdInt(int):
def __truediv__(self, other):
if other == 0:
import loging
logging.logg("tried to divide by " + other)
raise ValueError('divided by zero')
...
I've intentionally included at least three bugs in that method.
Obviously in practice only one is likely to occur at a time, but this
illustrates that even if the arguments to your div function are ints, in
this case a subclass of int, you might expect a ZeroDivisionError but
actually get:
* ImportError (`import loging`)
* AttributeError (`logging.logg`)
* TypeError (concatenate a string with a number)
* ValueError (explicit raise)
or any other exception at all. So your "total function" is still not
total: as soon as we move beyond the builtins written in C, literally
anything can happen and any promise you make in `div` can be only
advisory, not guaranteed.
To get your "total function" you have to catch *anything*, but that cure
is worse than the disease. That means that obvious bugs get suppressed
and turned into `Nothing()` when they should in fact raise.
And this brings us to where Java discovered that checked exceptions are
counter-productive. To satisfy the compiler and have a "total function",
anything which uses CheckedExceptions eventually ends up having the Java
equivalent of:
# Declared to only raise SomeError
try:
actual implementation
except:
# Catch anything and everything.
raise SomeError("something bad happened")
because that's the only way to end up with a total function in your
sense, given that subclasses can raise anything.
And that's why many of us are quite resistant to attempts to bring
checked exceptions to Python.
--
Steve
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/UADOFVBPRMECPDLHQVRBOZVHGYQRC7O7/
Code of Conduct: http://python.org/psf/codeofconduct/