首页 > 解决方案 > 如何使用 Kotlin 和 Arrow 执行程序

问题描述

我正在尝试使用 Kotlin 和 Arrow 学习一些函数式编程,通过这种方式,我已经阅读了一些类似以下的博文:https ://jorgecastillo.dev/kotlin-fp-1-monad-stack ,其中很好,我已经理解了主要思想,但是在创建程序时,我不知道如何运行它。

让我更明确地说:

我有以下代码:

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) }
}

如您所见,它将一些结果累积到 IO monad 中。我应该使用unsafeRunSync()from 箭头运行这个程序,但在 javadoc 上它说明如下:**NOTE** this function is intended for testing, it should never appear in your mainline production code!. 我应该提到我知道unsafeRunAsync,但就我而言,我想要同步。

谢谢!

标签: kotlinfunctional-programmingio-monadarrow-kt

解决方案


而不是跑步unsafeRunSync,你应该赞成unsafeRunAsync

如果你有myFun(): IO<A>并且想要运行它,那么你调用myFun().unsafeRunAsync(cb)where cb: (Either<Throwable, A>) -> Unit

例如,如果您的函数返回,IO<List<Int>>那么您可以调用

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

这将异步运行 IO 中包含的程序并将结果安全地传递给回调,如果 IO 抛出,它将记录错误,否则将打印整数列表。

unsafeRunSync出于多种原因,您应该避免,此处讨论。它是阻塞的,它可能导致崩溃,它可能导致死锁,它可能会停止你的应用程序。

如果你真的想将你的 IO 作为阻塞计算运行,那么你可以在这之前attempt()让你IO<A>成为IO<Either<Throwable, A>>类似于unsafeRunAsync回调参数。至少你不会崩溃。

而是unsafeRunAsync首选。另外,请确保传递给的回调unsafeRunAsync不会抛出任何错误,假设它不会。文档


推荐阅读