Should this example throw or `shift("boom")`? ```c...
# arrow
p
Should this example throw or
shift("boom")
?
Copy code
class Test : FreeSpec({
    "should this throw?" {
        effect {
            result { error("💥") }.bind { "boom" }
        }.toEither() shouldBe Either.Left("boom")
    }
})
Currently it throws.
Either.catch({ "boom" }) { error("💥") }.bind()
shifts as you’d expect.
on a similar note, should `EffectScope`’s
Result<B>.bind
use
shift(transform(throwable.nonFatalOrThrow()))
?
s
This should not be throwing but the assertion should pass.
@phldavies, I think I know what you're seeing.
result { }
doesn't catch, and this works.
Copy code
class Test : FreeSpec({
    "should this throw?" {
        effect {
            runCatching { error("💥") }.bind { "boom" }
        }.toEither() shouldBe Either.Left("boom")
    }
})
Does that make sense?
p
Yeah I think that’s what I ended up doing. I’m not entirely sure what the need for
result
is with
runCatching
available and the
bind
being on
Result<T>
. The only question I still have is if
.bind
should be aware of fatal/cancellation exceptions or not
The
Either.catch
equivalent does use
nonFatalOrThrow
- should
Result.bind
?
result {}
allows you to
shift
your Throwable instead of throw it, which I assume has better performance.
Copy code
either {
    result<Int> { shift(IllegalStateException("💥")) }.bind { "boom" }
} shouldBe Either.Left("boom")
does shift as expected.
With this in mind, I can kind of understand the reasoning to have a
result {}
computation that handles shifted throwables but unexpected are propagated. It was just surprising. If it’s expected to catch and shift any thrown throwables though, I’m not sure it has any extra utility over
runCatching{}
, unless I’m missing something
s
Ah okay, I see your confusion and it makes perfect sense! Perhaps we can clarify this in documentation somewhere. I would say the difference comes from
Either<Throwable, A>
vs
Result<A>
. The Kotlin Std exposes
Result<A>
and has explicitly stated in the passed that it on purpose catches all exceptions, also fatal ones. Arrow felt that changing those semantics on an existing type was confusing, so we left
Result<A>
as is. In contrast people can use
Either<Throwable, A>
which offers similar semantics as
Result<A>
with the only different that it does take into account
FatalError
just like all other data types and operators in Arrow.
So with that in mind
Result.bind
should not check
nonFatalOrThrow
, whilst
Either.catch
does.
This difference comes from
runCatching
vs
Either.catch
rather than
bind
.
p
Ah ok, understood - it now makes sense why
nonFatalOrThrow
is not used in
Result.bind
. 🙏