Hello FP people :wave: I'm exploring applying FP/a...
# arrow
t
Hello FP people 👋 I'm exploring applying FP/arrow across a micro service i'm building but I'm unsure what the best/right approach is in some areas. I've put together a short & simplified example which reflects my current thinking and I'd hugely appreciate any advice either 😂 in general but also specifically in terms of how to deal with the effects inside the Repo (should I use an Either, IO, Either.fx?!) and what that looks like?? I started looking at IO and .unsafeRunAsync, but the javadoc comment about never using it in production confused me as to how I actually get the block inside to execute?! Code snippet inside thread
Copy code
import arrow.core.Either
import arrow.core.flatMap
import arrow.core.left
import arrow.core.right
import com.google.cloud.firestore.FirestoreOptions
import java.util.*

fun controller(name: String): String {
    return Item
        .from(name)
        .flatMap { Repo.save(it) }
        .fold(
            { "Something went wrong: $it" },
            { it.id }
        )
}

object Item {
    fun from(name: String): Either<Failure, State> {
        return State(name = name).right()
    }
}

object Repo {
    private val repo = FirestoreOptions.newBuilder().build().service.collection("item")

    fun save(state: State): Either<Failure, State> {
        /**
         * What's the right approach here? I believe my options are:
         * 1 - Use Either with my Failure types below this is the limit of my knowledge currently :)
         * 2 - Use Either.fx ... unsure how to use this
         * 3 - Use IO ... unsure how to use this
         *
         */

        val result = repo
            .document(state.id)
            .create(state)
            .get() // Google's NoSQL library uses Promises

        return if (result == null) {
            Failure.Infrastructure.left()
        } else {
            state.right()
        }
    }
}

data class State(
    val id: String = UUID.randomUUID().toString(),
    val name: String
)

sealed class Failure(val reason: String) {
    object Domain : Failure("domain error")
    object Service : Failure("service")
    object Infrastructure : Failure("infrastructure error")
    object Controller : Failure("controller error")
}
s
If you’ve been looking into
IO
, then this might help. This was announced yesterday @ Kotliners, hopefully the video will be online soon. https://github.com/arrow-kt/arrow-fx/pull/169
This gives you the same functional API and guarantees as Arrow’s IO and covers its full API.
I’ll try to take a look at your snippet later 🙂
t
Ahh I did see the announcement will watch the video... I always like trying the latest and greatest!
arrow 1
s
All feedback is welcome 🙂
t
If I spot anything happy to contribute! No guarantees though still quiet new to FP
Is it in snapshot now?
s
We’re having some issues with the CI being overloaded by the test suite 😅
A snapshot should be available early next week when we had some time to figure out our CI situation
🙏 2
t
👍
Just playing around with IO.fx ... is this an ok approach?
Copy code
fun save(state: State) = runBlocking<Either<Failure, State>> {
        IO.fx {
            val result = repo
                .document(state.id)
                .create(state)
                .get() // Google's NoSQL library uses Promises

            if (result == null) {
                Failure.Infrastructure.left()
            } else {
                state.right()
            }   
        }.suspended()
    }
p
a couple of things to unpack here
your function is non-suspend. Meaning it’s synchronous, or blocking. Whichever name you prefer
if you attempt to do an asynchronous operation on it…it’s already a smell
it may end up deadlocking depending on threading requirements
if you make it suspend you can remove IO
Copy code
suspend fun save(state: State): Either<Failure, State> {
  val result = repo
                .document(state.id)
                .create(state)
                .get() // Google's NoSQL library uses Promises
 if (result == null) {
  return Failure.Infrastructure.left()
 } else {
  return state.right()
 }   
}
the caller may be the one sending this function to another thread
if you want to do that in the current function…for whatever reason
Copy code
suspend fun save(state: State): Either<Failure, State> = IO(IO.dispatchers().background()) { // start IO in another thread
  val result = repo
                .document(state.id)
                .create(state)
                .get() // Google's NoSQL library uses Promises
 if (result == null) {
  Failure.Infrastructure.left()
 } else {
  state.right()
 }   
}.suspended() // unwrap IO using suspension
now, the smell/unsafe part. If you really have no choice but to make it blocking
just replace
suspended
with
unsafeRunSync
Copy code
fun save(state: State): Either<Failure, State> = IO(IO.dispatchers().background()) {
  ...
}.unsafeRunSync()
runBlocking
is the
unsafeRunSync
of the kotlinx.coroutines library. It’ll work, it’s just mixing the libraries for the sake of it. kotlinx.coroutines isn’t necessary to use arrow.
t
Hey thanks for the detailed response 🙏 Everything you've said above makes sense. And ya smelly to use suspend with the already blocking operation ... not sure why I did that tbh! Just saw something new (IO) and panicked 😂 Atm, I can't see any reason in my codebase that .suspended() wouldn't work. So I'll stick with that for instances that we actually want to use a suspend From the controllers perspective, is that the typical workflow wrt FP/arrow?
👌🏼 1
Also as the call to the NoSQL db is already blocking, should it be wrapped in IO at all? I assume it should be because its a network call and intuitively IO seems appropriate here? But I could accomplish the same without using IO at all ..
Just reading though this: https://www.pacoworks.com/2019/12/15/kotlin-coroutines-with-arrow-fx/ useful post 👍 answering lots of questions 🙂
👍🏼 1