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