Hi! Playing with Arrow 2.0 (see <https://kotlinlan...
# arrow
j
Hi! Playing with Arrow 2.0 (see https://kotlinlang.slack.com/archives/C5UPMM0A0/p1670434541145989) I now have one more question: I'm using
context(Raise<ReadError>) fun readInt(json: Json): Int
instead of
fun readInt(json: Json): Either<ReadError, Int>
. Now I want to accumulate several of these errors, like we do with
Either
(
readInt(jsonA).zip({readInt(jsonB)},{a, b -> a + b})
). What would be the idiomatic way? I hoped for something like
zip({readInt(jsonA}, {readInt(jsonB)}, {a,b -> a + b})
but it seems I can't find it in the 2.0.0 SNAPSHOT.
I tried to implement such a function myself:
Copy code
context (Raise<NonEmptyList<E>>)
fun <E, A, B, Z> zip(
    a: Raise<E>.() -> A,
    b: Raise<E>.() -> B,
    transform: (A, B) -> Z,
): Z {
    val aa = either(a)
    val bb = either(b)
    val z = aa.zip(bb, transform)
    return z.getOrRaise()
}
But
either(a)
seems to shortcircuit and return from my zip function with just the first error. I can't understand why.
s
Hey, such a function is not available yet, and the one you shared should be correct, it should not be short-circuiting 🤔 That would be a bug.
c
does getOrRaise() exist in the 2.0.0-SNAPSHOT? i can´t find it.
s
No, that doesn't exists. These kind of extension functions need
context
receivers but it's actually just
bind
, right?
getOrRaise
if maybe a nicer name if you're always working with
Raise
🤔
j
Hey, such a function is not available yet, and the one you shared should be correct, it should not be short-circuiting 🤔 That would be a bug.
I will try to write some simplified example to see if I isolate the bug or I find something I was doing wrong. If it is the former, I'll reach you to see if it helps.
Thanks again for your help! As another suggestion, I'd say providing functions and documentation of how to accumulate errors in effects (probably without
Either
) would be really interesting.
s
Yes, nothing besides
zip
is available for
Raise
yet. Feel free to open a issue for that on Github. Problem with
traverse
like operators is that it requires context receivers to do it nicely. https://github.com/arrow-kt/arrow/pull/2872
j
I will try to write some simplified example to see if I isolate the bug or I find something I was doing wrong. If it is the former, I'll reach you to see if it helps. (edited)
I managed to get a simpler test failing... only to discover it seems to fail only when using context receivers. In case anyone wonders:
Copy code
context(Raise<String>) 
fun <A> transformError(eff: context(Raise<String>) () -> A): A =
    eagerEffect { eff() } recover { raise("$it!") }

context(Raise<NonEmptyList<E>>)
fun <E, A, B, Z> zip(a: Raise<E>.() -> A, b: Raise<E>.() -> B, transform: (A, B) -> Z): Z =
    either(a).zip(either(b), transform).bind() 

class NestedRaiseTest : FunSpec() {

    context(Raise<String>) fun fail1(): String = transformError { raise("hello") }
    context(Raise<String>) fun fail2(): String = transformError { raise("world") }

    init {

        test("zip should zip the effects") {

            eagerEffect<NonEmptyList<String>, Any> {
                zip({ fail1() }, { fail2() }) { _, _ -> "No failures??" }
            }.fold(
                { Assertions.assertEquals(nonEmptyListOf("hello!", "world!"), it) },
                { Assertions.fail<Unit?>("Unexpected success $it") }
            )
        }

        test("Inlined functions should work exactly the same") {
            eagerEffect<NonEmptyList<String>, Any> {
                zip({ transformError { raise("hello") } }, { transformError { raise("world") } }) { _, _ -> "No failures??" }
            }.fold(
                { Assertions.assertEquals(nonEmptyListOf("hello!", "world!"), it) },
                { Assertions.fail<Unit?>("Unexpected success $it") }
            )
        }
    }
}
The second test is just the result of inlining
fail1
and
faill2
, which should be equivalent AFAIK. But the first test passes and the second one fails with
Copy code
Expected :NonEmptyList(hello!, world!)
Actual   :NonEmptyList(hello, world)
The very same test works flawlessly if I use simple receivers instead of context receivers:
fun <A> Raise<String>.transformError...
etc.
s
Ye, that is definitely a compiler plugin. They're still experimental, and sometimes they apply optimisations that aren't valid that result in these kind of situations 😕
Bugs that have been ironed out with regular receivers.
j
It turns out I was mixing context receiers and regular receivers in the zip function above. The parameters used regular receivers. The compiler was ok with that but the behaviour changed. If everything is correctly written with context receivers it also works.