1

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!

3
  • With monads you can't freely get the result of a monadic computation out of the monad. That is the fundamental trait of every monad. Instead of unwrapping the value you have a bunch of combinators to work with value inside the monadic context. However, if you exactly know what you are doing you can use unsafeRunSync, even in production code. But it should be the exception rather than the rule. Commented Nov 25, 2019 at 10:16
  • I want to provide a User as a result on an API call and I don't know how to provide the User instead of the EitherIO<UserError, User?>. Commented Nov 25, 2019 at 11:36
  • 2
    Make your API suspend and use myIO.suspended() to call on it. Commented Nov 30, 2019 at 10:49

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.