https://kotlinlang.org logo
#arrow
Title
# arrow
j

Jörg Winter

10/16/2023, 5:48 AM
Hi, with Arrow 1.2.1, would this be the most idiomatic way to use high-level concurrency (parZip) and not short-circuiting (by using Either results) and finally, if all results are Either.right, making the insert in some database (in this case) ? Logging/Aggregating also goes in a separate call to "onLeft", as I think there is no handler for it in "zipOrAccumulate" (?)
Copy code
suspend fun collectData() {
    parZip(
        { catch { fetchFromHttp1() } },
        { catch { fetchFromHttp2() } },
        { catch { fetchFromHttp3() } }
    ) { result1, result2, result3 ->
        val result = zipOrAccumulate(result1, result2, result3) { r1, r2, r3 ->
            dbRepository.save(Entity(r1, r2, r3))
        }
        result.onLeft {
            log.error(it.map { it.message }.joinToString("\n", "\n"))
        }
    }
}
s

simon.vergauwen

10/16/2023, 6:00 AM
Hey @Jörg Winter, There is also
parZipOrAccumulate
.
Copy code
suspend fun collectData() {
    parZipOrAccumulate(
        { catch { fetchFromHttp1() }.bind() },
        { catch { fetchFromHttp2() }.bind() },
        { catch { fetchFromHttp3() }.bind() }
    ) { r1, r2, r3 ->
      dbRepository.save(Entity(r1, r2, r3))
        .onLeft {
          log.error(it.map { it.message }.joinToString("\n", "\n"))
        }
    }
}
j

Jörg Winter

10/16/2023, 6:04 AM
I am using 1.2.1 but I can not use that construct, seems to require transform fn as first param, and bind is deprecated (?)
a

Alejandro Serrano.Mena

10/16/2023, 7:00 AM
oh, weird, bind should never be deprecated (unless used in Effect)
j

Jörg Winter

10/16/2023, 7:06 AM
parZipOrAccumulate is not even suggested by Intellij in my code, so I am not sure if I am in the necessary context maybe…
s

simon.vergauwen

10/16/2023, 8:06 AM
Aha.. it's defined on
Raise<E>
. https://apidocs.arrow-kt.io/arrow-fx-coroutines/arrow.fx.coroutines/par-zip-or-accumul[…]A,%20B,%20C,%20D,%20F,%20G,%20H,%20I,%20J)%20-%3E%20K):%20K I think you need to call:
Copy code
suspend fun collectData() = either {
    parZipOrAccumulate(
        { catch { fetchFromHttp1() }.bind() },
        { catch { fetchFromHttp2() }.bind() },
        { catch { fetchFromHttp3() }.bind() }
    ) { r1, r2, r3 ->
      dbRepository.save(Entity(r1, r2, r3))
        .onLeft {
          log.error(it.map { it.message }.joinToString("\n", "\n"))
        }
    }
}
There is 1
bind
that has been deprecated but it's from a long long time ago. There was an
object ResultEffect
from the pre-1.x DSLs, that accidentally leaked into the public API 😕 Since it's an
object
it can be top-level imported, and it's basically
Either<Throwable, A>#getOrThrow
. We probably should've changed it to
HIDDEN
in 1.2.x
j

Jörg Winter

10/16/2023, 12:23 PM
trying your latest suggestion gives me:
or this, without the type parameters:
s

simon.vergauwen

10/16/2023, 12:32 PM
Could you provide a small snippet with imports?
j

Jörg Winter

10/16/2023, 1:01 PM
Copy code
import arrow.core.Either.Companion.catch
import arrow.core.raise.either
import arrow.fx.coroutines.parZipOrAccumulate
import org.springframework.stereotype.Service

@Service
class DataCollectionService(
) {

    suspend fun collectAndSave() {
        either {
            parZipOrAccumulate(
                { catch { fetchSomeHttp1() }.bind() },
                { catch { fetchSomeHttp2() }.bind() },
                { catch { fetchSomeHttp3() }.bind() }
            ) { result1: String, result2: String, result3: String ->
                // TODO what ever with results
            }
        }
    }

    private fun fetchSomeHttp1(): String {
        return "r1"
    }

    private fun fetchSomeHttp2(): String {
        return "r2"
    }

    private fun fetchSomeHttp3(): String {
        return "r3"
    }

}
..in which
s

simon.vergauwen

10/16/2023, 1:51 PM
Ah, ye it's not able to automatically infer the type 😞 So I think you need,
either<Throwable, A> { }
j

Jörg Winter

10/16/2023, 3:58 PM
yep.. almost. I had to provide a transformation as 1st parameter also, now it works nicely:
Copy code
either<Throwable, Unit> {
    parZipOrAccumulate(
        { throwable, throwable2 -> RuntimeException(throwable.message + throwable2.message) },
        { catch { fetchSomeHttp1() }.bind() },
        { catch { fetchSomeHttp2() }.bind() },
        { catch { fetchSomeHttp3() }.bind() }
    ) { result1, result2, result3 ->
        // some side-effect of type Unit
    }
}.onLeft { left -> log.error(left.message) }
🎉
Thanks
definetly nicer than my initial attempt
s

simon.vergauwen

10/17/2023, 6:29 AM
Oh, I'm sorry 😄 If you put
either<NonEmptyList<Throwable>, Unit> { }
you don't need to extra lambda, but if you want to stick to throwable that is better. Although I'd use
throwable.apply { addSuppressed(throwable2) }
4 Views