getkyo / kyo

Toolkit for Scala Development
https://getkyo.io
Apache License 2.0
527 stars 44 forks source link

ZIO-inspired error handling #651

Open fwbrasil opened 4 weeks ago

fwbrasil commented 4 weeks ago

Review Abort, Result, and related kyo-combinators methods to follow a naming pattern similar to ZIO's since it's quite mature and it would be helpful to people coming from ZIO. We should also review whether the APIs provide similar semantics to ZIO. Context: https://github.com/getkyo/kyo/pull/644#discussion_r1749345497

sideeffffect commented 4 weeks ago

I believe the ZIO model for errors is brilliant and kyo would greatly benefit from it:

sideeffffect commented 2 weeks ago

Btw, have you @fwbrasil considered constraining Result errors only to Exception? Like

opaque type Result[+E <: Exception, +A] >: (Success[A] | Failure[E]) = Success[A] | Failure[E]

E stands for those typed, recoverable/expected errors, right, but in my experience they should still always be subclasses of Exception for good interoperability with the JVM and ecosystem around it.

fwbrasil commented 2 weeks ago

Btw, have you @fwbrasil considered constraining Result errors only to Exception?

I think @hearnadam also suggested that some time ago. The intent was make Result have the features of both Either and Try and allow Abort for any value like in ZIO. I haven't seen much benefit in the approach yet, though. There's only the swap method that might not make sense if we add this restriction. @johnhungerford can you share you thoughts? I added swap inspired in the equivalent combinator method.

fwbrasil commented 2 weeks ago

I believe the ZIO model for errors is brilliant and kyo would greatly benefit from it:

  • typed channel for recoverable/expected errors
  • untyped channel for unrecoverable/unexpected failures

I think that's what have already given that Result and Abort handle both typed (Result.fail) and untyped failures (Result.panic).

johnhungerford commented 2 weeks ago

I definitely would not want to restrict errors to Exception in kyo. For instance, we should be able to use Abort as an Option-like effect by aborting with Maybe.Empty (or whatever it's called), which is not an exception.

fwbrasil commented 2 weeks ago

I don't find Abort[Maybe.Empty] very useful. In most cases, I prefer returning a regular Maybe so the caller has to handle the effect immediately and, in cases like a missing config, a specific exception seems better since it can communicate the cause of the abort more clearly. @johnhungerford do you mind sharing more on why you feel that's a useful pattern? To be honest, I'm still struggling to see the value of tracking Abort[Throwable] as well since it's essentially what the always-present Panic is 🙃

johnhungerford commented 2 weeks ago

A < (S & Abort[Maybe.Empty]) is equivalent to OptionT[S, A]: it combines the Option monad with other effects. I find this very useful in any case where I need to abstract over the composition of optional values while also abstracting over other effects. For instance, say you are writing a function to generate a result from multiple functions that return optional values asynchronously. In this case, you may want to lift those values to < Abort[Maybe.Empty] and let any empty value short-circuit the logic.

I agree in this case you probably want to then convert the A < (Abort[Maybe.Empty] & Async) back to an Option[A] < Async at the end of your function, but you still need to use Abort within the function. I also agree than in more cases you will want to preserve some information along with the missing value, but I have definitely encountered plenty of situations where I'm dealing with straightforwardly optional values. I've used this pattern plenty in ZIO.

sideeffffect commented 2 weeks ago

That seems like an abuse of Abort, whose purpose is to track typed, recoverable/expected errors. It's not that I'm morally condemning writing code like that, it will "work", but maybe it is a symptom of a lack of an OptionT-like effect in Kyo?

Obviously you have to do it like that in ZIO, because that's just how ZIO is, you don't have much choise. But isn't the point of Kyo to be more flexible, making it easy to add such an effect, which would be analogous to OptionT?

johnhungerford commented 2 weeks ago

If Abort were simply for errors, it would be called Error. Abort is for "aborting," or short-circuiting a computation. Why would it matter whether this is for errors or empty values? There used to be a separate Option effect in Kyo, but it was removed, since was basically just an alias for Abort[None].

A < Abort[B], represents a computation that may complete with a value of type A or abort with a value of type B. This way of thinking about aborts is consistent with both errors and options.

Again, in most cases I would probably handle A < Abort[Maybe.Empty] to Maybe[A] < Any at the end of a function, but I think it makes perfect sense to use Abort to abstract over short-circuiting missing values.

steinybot commented 1 week ago

To be honest, I'm still struggling to see the value of tracking Abort[Throwable] as well since it's essentially what the always-present Panic is 🙃

For me the same is true about Abort[Nothing] which you get if you just use Abort.panic with no other failure. In https://github.com/getkyo/kyo/pull/690/files I had incorrectly assumed could safely be eliminated and would be handled by the kernel. Abort[Nothing] is especially problematic because you cannot derive a ClassTag[Nothing] (or a Tag[Nothing] but that could be solved although that might introduce other problems). I think it is worth considering what panic only looks like going forward (if it should even exist).

sideeffffect commented 7 hours ago

If Abort were simply for errors, it would be called Error.

Fair enough. @fwbrasil do you think kyo would benefit from an effect dedicated purely to typed recoverable/expected errors?