James Yox
05/06/2023, 7:32 PM@Test
fun eitherAttempt() = runTest {
val success: Either<String, Int> = 1.right()
val failure: Either<String, Int> = "fail".left()
val actual = either {
val test = attempt { failure.bind() } catch { "fail" }
"${success.bind()} : $test"
}
assertEquals(
expected = "1 : fail".right(),
actual = actual
)
}
I can "fold" the result of failure.bind() using the attempt function in either's effect. In this case the test passes.
Now for Ior things go differently
@Test
fun iorAttempt() = runTest {
val greatSuccess: Ior<Nel<String>, Int> = 1.rightIor()
val semiSuccess: Ior<Nel<String>, Int> = Ior.Both("little fail".nel(), 2)
val greatFailure: Ior<Nel<String>, Int> = "epic fail".nel().leftIor()
val actual = ior(Semigroup.nonEmptyList()) {
val test = attempt<Nel<String>, Int> { greatFailure.bind() } catch { "fail" }
"${greatSuccess.bind()} : ${semiSuccess.bind()} : $test"
}
assertEquals(
expected = Ior.Both(nonEmptyListOf("little fail", "big fail"), "1 : 2 : fail"),
actual = actual
)
}
This test fails. I somewhat expected the Semigroup.nonEmptyList() stuff to maybe not combine inside the attempt { } block but what actually happens is that I get
Expected :Ior.Both(NonEmptyList(little fail, big fail), 1 : 2 : fail)
Actual :Ior.Left(NonEmptyList(epic fail))
The bind() simply short circuits out of the ior { } effect entirely not respecting the attempt { } effect. This seems inconsistent, but I might just not be understanding something.simon.vergauwen
05/07/2023, 9:06 AMior
DSL introduces a separate receiver (IorEffectScope
) IorRaise
to allow for error accumulation for Ior.Both
but this is not available inside (attempt
) recover
.
This means it uses the short-circuit behavior from (EffectScope
) Raise
instead.
A potential solution:
Define (attempt
) recover
inside (EffectScope
) IorRaise
with the desired behavior, having it define their will make it have precedence.
That would fix this inconsistency, what do you think? Could you make a GitHub issue for this? I think it deserves this fix.James Yox
05/07/2023, 5:30 PMbind()
from the IorEffectScope
and figured it should be hitting the EffectScope
bind but didn't want to speculate too much here. It sounds like this can potentially be fixed. I wouldn't mind trying to fix it. Might be a good way to learn more about Arrow. In any case I can still make the Issue on GitHub.
I'm also not on 1.20-RC yet though I plan to migrate soon. Really want to test out 2.0 but still desperately waiting for multiple context receivers support on Kotlin multiplatform before using them in my current project.simon.vergauwen
05/08/2023, 6:41 AMbut still desperately waiting for multiple context receivers support on Kotlin multiplatform before using them in my current project.I think we all are 😭 but this shouldn't be blocking for 1.2.0-RC/2.0. If you have any issue migrating or adopting 1.2.0-RC please let us know 🙏 I haven't seen any issue on Github, if you're interested on contributing I'd be more than happy to help and guide you with any questions or doubts you have ☺️ Feel free to create an issue, and I can assign you to it and we can discuss further details in the issue itself, and/or any WIP PR on Arrow 😉
James Yox
05/09/2023, 2:05 AMeither { }
is actually somewhat unique in that it does not have a special Raise class. It's bind() method is sort of tacked into the Raise<in Error>
class itself.
I dont know if this is even valid but I can do something sort of similar with option { }
@Test
fun optionAttempt() = runTest {
val some = 1.some()
val none: Option<Int> = None
val actual = option {
val test = recover<None, Int>({ none.bind()}) { 2 }
"${some.bind()} : $test"
}
assertEquals(expected = "1 : 2".some(), actual = actual)
}
This also fails with actual being Option.None
In theory a similar fix to what you propose could work I think. I'll try to get a WIP PR out tomorrow though.James Yox
05/10/2023, 1:15 AM