option
option copied to clipboard
Introduce a safe eval function
Hi there - I love this little library, but I think some utility functions would really be beneficial for migrating exception code to using results.
Some thoughts, similar to the dry-returns
library, we should introduce a safe
function for a following use case:
Imagine refactoring this piece of (very hypothetical code):
def foo(a, b, c):
try:
a(b, c)
except ZeroDivisionError:
raise ValueError("cannot divide by zero")
except Exception:
raise TypeError("What?")
It can be very easily refactored with the introduction of safe
function:
from functools import wraps
from option.result import Result
def _safe(func):
@wraps(func)
def safe_chain(*args, **kwargs):
z = Result.Ok(func)
try:
return z.map(lambda x: x(*args, **kwargs))
except Exception as e: # noqa
return Result.Err(e)
return safe_chain
then refactoring foo
becomes:
def foo(a, b, c):
a = _safe(a)
rval = a(b, c)
if rval.is_ok:
return rval
if rval.unwrap_err() is ZeroDivisionError:
return Result.Err("cannot divide by zero")
else:
return Result.Err("what?")
This works very well for languages that do not support try/except for control flows, which is something that can be mapped to non-Turing complete language subsets such as Starlark.
Thoughts?
The code sample you provided is a little confusing to read, but are you suggesting that there should be a decorator that converts exceptional code into one that returns Result
?
Love this idea, safe
would be very cool, but the return type would be Result[T, Exception]
, which is not the finest grain for the error type.
I ended up having something similar, which I call handle
:
E = TypeVar('E', bound=BaseException)
T = TypeVar('T')
def handle(err_sumtype: type[E], fn: Callable[[], T]) -> Result[T, E]:
"""
Encapsulates a function call that expects to return `T` but may fail with
exceptions `err_sumtype` (`E`) to return an `option.Result`
"""
try:
return Result.Ok(fn())
except err_sumtype as e:
return Result.Err(e)
In this case, your foo
becomes:
def foo_result(may_err_fn, x, y) -> Result[T, str]:
# output: Result[T, ZeroDivisionError | Exception]
output = handle(ZeroDivisionError | Exception, lambda: may_err_fn(x, y))
if output.is_ok:
return output
match output.unwrap_err():
case ZeroDivisionError(_):
return Result.Err("cannot divide by zero")
case _:
return Result.Err("what?")
def foo(may_err_fn, x, y):
return foo_result(may_err_fn, x, y).unwrap()