stateless icon indicating copy to clipboard operation
stateless copied to clipboard

A better way to handle exception?

Open khanetor opened this issue 11 months ago • 1 comments

Hi there,

Thank you so much for this gem. I have been using Effect.ts in Typescript, and I think what you did here is very close to Effect.ts.

There is one thing I am trying to do, but due to the lack of skill, I haven't managed to yet.

Do you think it is possible to create a handle function such that as we specify a handler function, it should return an effect without the handled exception?

It should look something like this, but I think type subtraction/exclusion is not possible in Python yet.

def handle(
  effect: Effect[Dep1, Exception1 | Exception2, T1],
  error_tag: Exception1,
  handler: Callable[[Exception1], Effect[Dep2, Exception3, T2]]
) -> Effect[Dep1 | Dep2, Exception2 | Exception3, T1 | T2]:
  ...

khanetor avatar Jan 14 '25 12:01 khanetor

Hi there! Sorry about the delay in response, I have been out making adventures with other projects 😅

I think type subtraction/exclusion is not possible in Python yet.

Correct, sadly I don't think there is anyway to type what you want. For specific errors you can of course do:

from stateless import catch, throw, Try


def some_effect() -> Try[RuntimeError | IOError | ZeroDivisionError, str]:
    ...


def handle_some_errors() -> Try[RuntimeError | ZeroDivisionError, str]:
    result = yield from catch(some_effect)()
    match result:
        case str(result):
            return result
        case IOError():
            return "default value on io error"
        case e:
            # this abomination is to make the type checker
            # shut up about this code path missing a return
            return (yield from throw(e))

If you come up with a good pattern for this, please do share! 😄

suned avatar Feb 27 '25 20:02 suned

Hi again. It only took me 9 months, but I think I may have come up with a solution to this problem 😄

It seems a function with this signature:

def catch_just(
    *e: Type[E2],
) -> Callable[[Callable[P, Effect[A, E2 | E, R]]], Callable[P, Effect[A, E, R | E2]]]:
    ...

Is possible, and produces the expected types both with mypy and pyright. I haven't tested any other type checkers.

For example:

from stateless import Runtime, Success, Try, catch_just, throws


@throws(ValueError, ZeroDivisionError)
def f() -> Success[str]:
    raise ZeroDivisionError()


def use_f() -> Try[ValueError, str]:
    maybe_s = yield from catch_just(ZeroDivisionError)(f)()
    reveal_type(maybe_s)  # revealed type is: ZeroDivisionError | str
    match maybe_s:
        case ZeroDivisionError():
            return "default value"
        case s:
            return s

suned avatar Sep 13 '25 20:09 suned

I just released an implementation of this for version 0.5.3. Check out the readme of the latest release for details.

suned avatar Sep 15 '25 20:09 suned