Как реализовать шаблон железной дороги в Kotlin/Arrow.kt для продюсерского канала

Я исследую Kotlin Coroutines & Channels в своем текущем приложении для Android.

У меня есть следующий код, который управляет удаленными вызовами API и контролирует побочные эффекты пользовательского интерфейса.

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

Приведенный выше код дает мне желаемый результат, однако я хотел бы преобразовать его во что-то, напоминающее «Железнодорожный шаблон» функционального программирования https://android.jlelse.eu/real-world-functional-programming-with-kotlin-arrow-b5a98e72f5e3

где мой процесс

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

который будет «короткое замыкание» либо при сбоях любого шага, либо когда History «isCompleted»

можно ли добиться такого стиля вызова в Котлине? и/или Kotlin и Arrow.kt?


person Hector    schedule 22.11.2019    source источник
comment
Не уверен, что делает ваш код... но да, с помощью Kotlin и Arrow можно достичь программирования, ориентированного на железную дорогу. Проверьте это: arrow-kt.io/docs/patterns/monad_comprehensions   -  person LordRaydenMK    schedule 23.11.2019
comment
Если каждый из них возвращает наблюдаемое, вы можете сделать это так. См. RxJava и RxKotlin   -  person Mahdi-Malv    schedule 26.11.2019
comment
Концепция программирования, ориентированная на железную дорогу, работает в Kotlin с помощью инфиксных функций для одного и того же типа ввода и вывода с разными результатами. Можете ли вы предоставить дополнительную информацию о своем коде (Что такое функции и ввод-вывод), чтобы я мог лучше помочь вам с вашим подходом?   -  person Jeel Vankhede    schedule 29.11.2019
comment
Вы можете реализовать ROP без Arrow — gist.github.com/harikrishnan83/3c4359bbb91e57eba7b962dc9300c6cc. Даже со стрелкой код может выглядеть немного менее читаемым (плоские карты могут мешать чтению шагов, как вы написали в своем вопросе). Возможно, вам потребуется добавить инфикс поверх этого gist.github.com/harikrishnan83/a16ef8f2e6f9287482adc2b29fbb23f2   -  person HariKrishnan    schedule 14.06.2020


Ответы (1)


Вы можете использовать от Arrow-kt.

Вы можете использовать Either's mapLeft(), map(), flatMap().

Если результат Exception, используйте mapLeft(). Возвращаемое значение из mapLeft() будет новым Left в результате Either, например, возврат будет String, результатом будет Either<String, List<Any>>. В случае, если результат Right, т.е. List<Any>, mapLeft() будет пропущен, но тип результата все равно изменится, поэтому у вас будет тип Either<String, List<Any>> со значением Right<List<Any>>. Вы также можете вернуть тот же Exception из mapLeft(), если вы так выберете

В случае, если вам не нужно обрабатывать определенные ошибки, вы можете просто связать map() и flatMap(). map() в основном является mapRight(), а flatMap() полезен, когда вы хотите цепочку вызовов, т. е. где-то в цепочке есть получатель List<Any>, который может дать сбой, и вы хотите обрабатывать Exception таким же образом с Either для этого вызова, вы можете просто вернуть новый Either из flatMap()

Код будет выглядеть примерно так

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))
            }
    }
person logcat    schedule 29.11.2019
comment
Интересно, почему нет метода .onLeft { ... } для любого типа. Выполнение mapLeft, конечно, работает, но постоянно возвращать его неудобно. Я предпочитаю .also { if (it.isLeft()) ... } в настоящее время. В качестве альтернативы .apply { if (isLeft()) ... } - person Zordid; 02.03.2021
comment
@Zordid Я думаю, так что вы можете цеплять один вызов за другим и обрабатывать ошибки для всех из них в одном месте. Иначе можно вернуться ни с чем. Я думаю, функция пользовательского расширения .onLeft сделает эту работу. И iirc в реальном продукте у нас была функция расширения .mapLeftReturn, которая опустила возвраты - person logcat; 03.03.2021