When writing test for new(0.11.0) `toIO()` (mentio...
# arrow-contributors
h
When writing test for new(0.11.0)
toIO()
(mentioned today in #arrow), I saw that
attempt
is like:
Copy code
fun attempt(): IO<E, Either<Throwable, A>>
While in Scala Cats there is:
Copy code
val x: IO[Either[Int, String]] = IO[Either[Int, String]] { Left(1) }
val y: IO[Either[Throwable, Either[Int, String]]] = x.attempt
Copy code
def attempt: IO[Either[Throwable, A]]
Won’t be more intuitive to type it like this? Also in actual form I am unable to call
IO.attempt().unsafeRunSync()
, because
unsafeRunSync
is like:
Copy code
fun <A> IOOf<Nothing, A>.unsafeRunSync(): A
b
That must be the new Bifunctor IO on 0.11.0. Arrow has
IO<E, A>
whereas cats-effect has
IO[A]
(and
E
is implicitly fixed to
Throwable
)
IO<E, A>.attempt: IO<E, Either<Throwable, A>>
enables translation of
Throwable
into a custom error hierarchy
E
No such translation can be done in cats-effect
There is some debate - I think? - as to whether the complexity of Bifunctor IO is justified over Functor IO
h
No, i did not meant that. I meant that in cats
IO[Either[E, A]]
is nearly (nested mapping) the same as arrow 0.11.0
IO<E, A>
. Before BIO implementation allowed
.attempt().unsafeRunSync()
chain, but BIO does not, And I wish why
attempt
result goes to right value of IO, but not to error side. Wouldn’t it be less confusing to
IO<E, A>.attemt(): IO<Throwable, Either<E, A>>
?
b
I have to think that
IO<E, A> -> IO<Throwable, Either<E, A>>
breaks some monad laws, but I'm not enough of a category theorist to say for sure
j
No laws apply here, so it does not matter. It's purely a style choice. Handling the inner `IO`'s failure case is by far `IO<E, A>`'s weakest point. It has two valid
MonadError
instances, one for
Throwable
and one for
E
and both have valid use cases! The instance for
Throwable
is especially important when catching unhandled errors which will eventually appear. The instance for
E
is perfect for handling domain errors and expected error cases... So anyway: I like the idea of going
IO<E, A>.attempt(): IO<Throwable, Either<E, A>>
it neatly makes the unexpected/unhandled error visible, but it still requires multiple steps to get to the
Throwable
. (But since that is probably expected anyway... 🤷)