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`. For 
the latter case exceptions work perfectly well as they are and it is both 
unhelpful and infeasible to annotate/document every exception - just let the 
exception bubble up and someone will handle it (might even be a process 
supervisor like supervisord)

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):

1. Extending the type of the functions co-domain (return type) and making the 
error case explicit (see https://returns.readthedocs.io/en/latest/index.html 
<https://returns.readthedocs.io/en/latest/index.html> for a library that 
implements this style of error handling)

```
def div(a: int, b: int) -> Maybe[float]:
  try:
      return Some(a / b)
  except ZeroDivisionError:
      return Nothing()
```

or

```
def div(a: int, b: int) -> Result[float, ZeroDivisionError]:
  try:
      return Success(a / b)
  except ZeroDivisionError as error:
      return Failure(error)
```

Now `div` does return a valid instance of `Maybe` (or `Result` if more a more 
detailed failure case is wanted) which is either something like  `Some(3.1415) 
` or `Nothing` (analogous to `None` ). The caller of the function then has to 
deal with this and mypy will correctly warn if the user fails to do so properly 
e.g.

´´´
div(1,1) + 1  # mypy will give a type error
´´´

2. Restricting the functions domain to values with defined behavior

```
def div(a: int, b: NonZeroInteger) -> float:
  return a / b
```

In this case the burden is put on the caller to supply valid inputs to the 
function and this effectively pushes the error handling/ input validation out 
of `div`



A language that does all this really well is F# (which like python is a 
multi-paradigm language that offers both object-oriented and 
functional-programming-oriented constructs). The trouble in python at the 
moment is that using something like `Maybe` and `Result` is not as nice to use 
as exceptions due to lack of a nice syntax i.e. for exceptions we have 
`try`/`except` and for functional error handling we’d need pattern matching.

_______________________________________________
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/KBBXERGVQ4YMBFGJL2TLGP6YWN3VR3AJ/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to