dry-python/returns

Equivalent feature to Rust ? operator

johnthagen opened this issue · 3 comments

btw, this library is super cool

I was trying out Result and one of the first things I wanted to do to get familiar with the Python version was to implement something similar from Rust.

This Rust by Example example shows how the ? operator does early return in an ergonomic way.

    fn op_(x: f64, y: f64) -> MathResult {
        // if `div` "fails", then `DivisionByZero` will be `return`ed
        let ratio = div(x, y)?;

        // if `ln` "fails", then `NonPositiveLogarithm` will be `return`ed
        let ln = ln(ratio)?;

        sqrt(ln)
    }

Now, this is a Rust language feature which might be hard to emulate in Python, but I was curious if returns has something similar. Perhaps along the lines of the deprecated try! macro?

What is the best way to handle this situation? Is matching on each intermediate Result the best way?

You can do something like (pseudo-code):

def reraise(func: Callable[_P, _R]) -> Callable[_P, ResultE[_R]]:
  def inner(*args: _P.args, **kwargs) -> ResultE[_R]:
    try:
       return Success(func(*args, **kwargs))
    except UnwrapFailedError as exc:
       return Failure(exc.inner_exception)

@reraise
def logic() -> int:
   return a().unwrap() + b.unwrap()

def a() -> ResultE[int]:
   return 1

def b() -> ResultE[int]:
   return 2

I also just discovered that flow is an ergonomic way to handle at least some early return issues (where there is a single value flowing through without side effects):

class MathError(Enum):
    DivisionByZero = auto()
    NonPositiveLogarithm = auto()

MathResult: TypeAlias = Result[float, MathError]

def div(x: float, y: float) -> MathResult:
    if y == 0.0:
        return Failure(MathError.DivisionByZero)

    return Success(x / y)


def ln(x: float) -> MathResult:
    if x <= 0.0:
        return Failure(MathError.NonPositiveLogarithm)

    return Success(math.log(x))

def op_flow(x: float, y: float) -> MathResult:
    return flow(
        div(x, y),
        bind(ln),
    )

Since this was more of a question, I will go ahead and close this issue to keep the tracker clean.

Maybe one day Python will add something like ? 😄