python/returns

Result & ResultE

wefree 2022. 9. 4. 21:37

https://returns.readthedocs.io/en/latest/pages/result.html

  • scala Either 와 비슷한듯
  • error 를 Exception 으로 고정해 scala Try 처럼 사용해도 좋을 듯, ResultE[T] = Result[T, Exception]
  • Result.do 와 pipe 도 알아두자 (flow 대신에 Result.do 를 사용하자?)

 

from typing import Callable

from returns.pipeline import flow, pipe
from returns.pointfree import bind
from returns.result import Result, Success, Failure, ResultE

x: ResultE[int] = Success(2)
# x: Result[int, Exception] = Success(2)


y: ResultE[int] = Success(3)
# y: Result[int, Exception] = Success(3)


def sum_of(x: int, y: int) -> int:
    return x + y


# scala 에서의 for-comprehension ?
z = Result.do(
    a + b  # sum_of(a, b) 로도 표현 가능
    for a in x
    for b in y
)  # Success(5)


#############################################################

def f(x: int) -> ResultE[int]:
    return Success(x + 1)


def g(x: int) -> ResultE[int]:
    return Success(x * 2)


# 시작 값으로 computation (이것 보다는 아래의 Result.do 를 쓰는 것이 좋아 보임)
flow(
    1,  # instance
    f,  # function
    bind(g)  # function
)  # Success(4)

# 위의 flow 를 Result.do 로 표현
Result.do(
    y
    for x in f(1)
    for y in g(x)
)  # Success(4)

# function composition: 시작 값이 없이 function composition
h: Callable[[int], ResultE[int]] = pipe(
    f,
    bind(g)
)
h(1)  # Success(4)


#############################################################

def recover(e: Exception) -> int:
    return 123


def recover_with(e: Exception) -> ResultE[int]:
    return Success(456)


def find_user(id: int) -> ResultE[int]:
    if id == 1:
        return Success(id + 100)
    else:
        return Failure(ValueError("invalid value"))


find_user(1).alt(recover)  # 101
find_user(2).alt(recover)  # 123

find_user(1).lash(recover_with)  # 101
find_user(2).lash(recover_with)  # 456

 

Result + Maybe

from returns.maybe import Maybe, Some
from returns.pipeline import is_successful
from returns.result import Success, ResultE, Failure, Result

# maybe: Maybe[int] = Some(3)
maybe: Maybe[int] = Maybe.empty
# result: ResultE[int] = Success(1)
result: ResultE[int] = Failure(ValueError)

z: Maybe[int] | ResultE[int] = Result.do(
    x + y
    for x in maybe
    for y in result
)

# 방법 1
v: int = z.value_or(9)
print(v)

# 방법 2
if is_successful(z):
    v = z.unwrap()
    print(v)
else:
    print('Error')

# 방법 3
match z:
    case Some(v) | Success(v):
        print(v)
    case Maybe.empty:
        print('Maybe Error')
    case Failure(e):
        print('Result Error')

 

Result v.s Maybe

from returns.converters import maybe_to_result, result_to_maybe
from returns.maybe import Maybe, Some
from returns.pipeline import is_successful
from returns.result import Result, Success, ResultE

x: ResultE[int] = Success(2)
a: Maybe[int] = result_to_maybe(x)

b: Maybe[int] = Some(3)
y: Result[int, None] = maybe_to_result(b)

c: Maybe[int] = Nothing
z: ResultE[int] = maybe_to_result(c, Exception('error'))  # Failure("error")
z.failure()  # Get failed value or raise exception.

is_successful(b)
is_successful(y)

 

@safe

from returns.result import Success, Failure, safe


# Will convert type to: Callable[[int], ResultE[float]]
@safe  
def divide(n: int, m: int) -> float:
    return n / m


match divide(1, 0):
    case Success(10):
        print('Result is "10"')
    case Success(value):
        print(f'result={value}')
    case Failure(ZeroDivisionError()):
        print('"ZeroDivisionError" was raised')
    case Failure(_):
        print('The division was a failure')

 

safe 직접 호출

from returns.result import ResultE, safe

def divide(n: int, m: int) -> float:
    return n / m


z: ResultE[float] = safe(divide)(1, 0)
print(z)  # <Failure: division by zero>