Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a safe eval function #7

Open
mahmoudimus opened this issue May 29, 2021 · 2 comments
Open

Introduce a safe eval function #7

mahmoudimus opened this issue May 29, 2021 · 2 comments

Comments

@mahmoudimus
Copy link

mahmoudimus commented May 29, 2021

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?

@MaT1g3R
Copy link
Owner

MaT1g3R commented Mar 30, 2022

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?

@Pegasust
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants