简体   繁体   中英

How to Implement “Railway Pattern” in Kotlin/Arrow.kt for producer Channel

I'm investigating Kotlin Coroutines & Channels in my current Android application.

I have the following code that manages remote Api calls and controls UI Side effects

   private val historical: CompletableDeferred<List<Any>> = CompletableDeferred()
   private val mutex = Mutex()

    @ExperimentalCoroutinesApi
    fun perform(action: Action): ReceiveChannel<List<Any>> =
        produce {

          mutex.withLock {
            if (historical.isCompleted) {
                send(historical.getCompleted())
                return@produce
            }  

            send(action.sideEffects)
            val networkResponse = repository.perform(action)
            send(networkResponse.sideEffects)
            send(listOf(networkResponse)).also {
                    historical.complete(listOf(response))
                }
            }
        }

The above code gives me the desired result, however I would like to refactor it to something resembling the Functional Programming "Railway Pattern" https://android.jlelse.eu/real-world-functional-programming-with-kotlin-arrow-b5a98e72f5e3

where my process flow is

stepOne(Historical.completed)
.stepTwo(action.sideEffects)
.stepThree(getReaction())
.stepFour(reaction.sideEffects)
.finalStep(reaction)

which will "short circuit" on either failures of any step or when Historical "isCompleted"

is it possible to achieve this style of call in Kotlin? and/or Kotlin & Arrow.kt?

You can use Arrow-kt's Either .

You can use Either's mapLeft() , map() , flatMap() .

In case result is Exception use mapLeft() . Returned value from mapLeft() going to be new Left in result Either , for example return is String , result going to be Either<String, List<Any>> . In case result is Right , ie List<Any> , mapLeft() going to be skipped, but result type changes anyway, so you will have type of Either<String, List<Any>> with value of Right<List<Any>> . You can also return same Exception from mapLeft() if you choose so

I case you have no need in handling specific errors, you can just chain map() and flatMap() . map() is basically mapRight() and flatMap() is useful when you want to chain calls, ie somewhere in chain there is a receiver of List<Any> which can fail and you want to handle Exception the same way with Either for that call, you can just return new Either from flatMap()

Code gonna look something like this

fun perform(action: Either<Exception, Action>): ReceiveChannel<List<Any>> =
    produce {
        // if action is always right, you can start it as Right(action) but then first mapLeft does not make any sense
        if (historical.completed) action
            .mapLeft {
                // handle actions exception here
                // transform it to something else or just return it
                send(action.sideEffects)
                it
            }.flatMap {
                // handle right side of action either
                // assume here that repository may fail and returns Either<Exception, NetworkResponse>
                repository.perform(it)
            }.mapLeft {
                // handle repositorys exception here
                // transform it to something else or just return it
                send(it)
                it
            }.map {
                // handle network response
                send(listOf(networkResponse))
                historical.complete(listOf(networkResponse))
            }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM