Dina Bogdan
Dina Bogdan

Reputation: 4738

How to execute a program with Kotlin and Arrow

I'm trying to learn a bit of Functional Programming using Kotlin and Arrow and in this way I've already read some blogposts like the following one: https://jorgecastillo.dev/kotlin-fp-1-monad-stack, which is good, I've understand the main idea, but when creating a program, I can't figure out how to run it.

Let me be more explicit:

I have the following piece of code:

typealias EitherIO<A, B> = EitherT<ForIO, A, B>

sealed class UserError(
        val message: String,
        val status: Int
) {
    object AuthenticationError : UserError(HttpStatus.UNAUTHORIZED.reasonPhrase, HttpStatus.UNAUTHORIZED.value())
    object UserNotFound : UserError(HttpStatus.NOT_FOUND.reasonPhrase, HttpStatus.NOT_FOUND.value())
    object InternalServerError : UserError(HttpStatus.INTERNAL_SERVER_ERROR.reasonPhrase, HttpStatus.INTERNAL_SERVER_ERROR.value())
}


@Component
class UserAdapter(
        private val myAccountClient: MyAccountClient
) {
    @Lazy
    @Inject
    lateinit var subscriberRepository: SubscriberRepository

    fun getDomainUser(ssoId: Long): EitherIO<UserError, User?> {
        val io = IO.fx {
            val userResource = getUserResourcesBySsoId(ssoId, myAccountClient).bind()
            userResource.fold(
                    { error -> Either.Left(error) },
                    { success ->
                        Either.right(composeDomainUserWithSubscribers(success, getSubscribersForUserResource(success, subscriberRepository).bind()))
                    })
        }
        return EitherIO(io)
    }

    fun composeDomainUserWithSubscribers(userResource: UserResource, subscribers: Option<Subscribers>): User? {
        return subscribers.map { userResource.toDomainUser(it) }.orNull()
    }
}

private fun getSubscribersForUserResource(userResource: UserResource, subscriberRepository: SubscriberRepository): IO<Option<Subscribers>> {
    return IO {
        val msisdnList = userResource.getMsisdnList()
        Option.invoke(subscriberRepository.findAllByMsisdnInAndDeletedIsFalse(msisdnList).associateBy(Subscriber::msisdn))
    }
}

private fun getUserResourcesBySsoId(ssoId: Long, myAccountClient: MyAccountClient): IO<Either<UserError, UserResource>> {
    return IO {
        val response = myAccountClient.getUserBySsoId(ssoId)
        if (response.isSuccessful) {
            val userResource = JacksonUtils.fromJsonToObject(response.body()?.string()!!, UserResource::class.java)
            Either.Right(userResource)
        } else {
            when (response.code()) {
                401 -> Either.Left(UserError.AuthenticationError)
                404 -> Either.Left(UserError.UserNotFound)
                else -> Either.Left(UserError.InternalServerError)
            }
        }
    }.handleError { Either.Left(UserError.InternalServerError) }
}

which, as you can see is accumulating some results into an IO monad. I should run this program using unsafeRunSync() from arrow, but on javadoc it's stated the following: **NOTE** this function is intended for testing, it should never appear in your mainline production code!. I should mention that I know about unsafeRunAsync, but in my case I want to be synchronous.

Thanks!

Upvotes: 1

Views: 386

Answers (1)

user1713450
user1713450

Reputation: 1429

Instead of running unsafeRunSync, you should favor unsafeRunAsync.

If you have myFun(): IO<A> and want to run this, then you call myFun().unsafeRunAsync(cb) where cb: (Either<Throwable, A>) -> Unit.

For instance, if your function returns IO<List<Int>> then you can call

myFun().unsafeRunAsync {  /* it (Either<Throwable, List<Int>>) -> */
  it.fold(
    { Log.e("Foo", "Error! $it") },
    { println(it) })
}

This will run the program contained in the IO asynchronously and pass the result safely to the callback, which will log an error if the IO threw, and otherwise it will print the list of integers.

You should avoid unsafeRunSync for a number of reasons, discussed here. It's blocking, it can cause crashes, it can cause deadlocks, and it can halt your application.

If you really want to run your IO as a blocking computation, then you can precede this with attempt() to have your IO<A> become an IO<Either<Throwable, A>> similar to the unsafeRunAsync callback parameter. At least then you won't crash.

But unsafeRunAsync is preferred. Also, make sure your callback passed to unsafeRunAsync won't throw any errors, at it's assumed it won't. Docs.

Upvotes: 1

Related Questions