kotlinfunctional-programmingeithervavr

Writing readable code using vavr Either in Kotlin while performing sequential operations


I have following piece of code written in Kotlin using vavr. The function performs a chain of operations one by one and if one of them fails, it returns Error. If all operations succeed, it returns SuccessObject. Left is assumed to be error and right the success flow. However, this function is difficult to read because of all the .map() constructs. Is there a way to simplify this code to make it more readable?

private fun performOperation(param: String): Either<Error, SuccessObject> {
  return performValidation(param)
    .map {
      initiateOperation(param)
        .map { result ->
          oneMoreValidation(param, result.property)
            .map { validationResult ->
              updateDatabase(param, validationResult, result)
            }
            .getOrElseGet { error -> left(error) }
        }
        .getOrElseGet { error -> left(error) }
    }
    .getOrElseGet { error -> left(error) }   
}

private fun performValidation(param: String): Either<Error, ValidationResponse> {}
private fun initiateOperation(param: String): Either<Error, InitOperationResponse> {}
private fun oneMoreValidation(param: String, property: String): Either<Error, OneMoreValidationResponse> {}
private fun updateDatabase(param: String, property: String, result: InitOperationResponse): Either<Error, SuccessObject> {}

I looked into combining, chaining Eithers. But none of them seems to simplify the code to make more readable. I also looked into this blog on Railway Oriented Programming. But all the methods accept the same param there. Any help would be appreciated.

EDIT: As pointed out by VLAZ and Hinse, .flatMap() could mitigate this. The function becomes:

private fun performOperation(param: String): Either<Error, SuccessObject> {
  return performValidation(param)
    .flatMap {
      initiateOperation(param)
        .flatMap { result ->
          oneMoreValidation(param, result.property)
            .flatMap { validationResult ->
              updateDatabase(param, validationResult, result)
            }
        }
    }
}

This is definitely an improvement. But the function still has clutter in terms of indentation and repeated use of .flatMap(). So is this the accepted way of implementing functions or can it still be improved?


Solution

  • Instead of nesting your maps, you can do one after the other like so:

    private fun performOperation(param: String): Either<Error, SuccessObject> {
      return performValidation(param)
        .flatMap {
          initiateOperation(param)
        }
        .flatMap { result ->
          oneMoreValidation(param, result.property)
        }
        .flatMap { validationResult ->
          updateDatabase(param, validationResult, result)
        }
    }