Happy Railway

Hadi published on
5 min, 903 words

Categories: Android

Wallpaper

This post is on the tail of Railway Oriented Programming in Kotlin by Antony Harfield. So you need to read it first and continue here. As it’s obvious I really liked it and tried it out. It needs every process have a result like The generic result

sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val errorMessage: String): Result<T>()

Then we could chain the processes and create our railway like

input to ::parse then ::validate then ::send otherwise ::error

It’s so satisfying and shows what we want to achieve so clearly until we notice the Result is not native to every processes. It’s a general wrapper. For instance the Result of ::validate will be more informative if it would be like

sealed class ValidationResult
data class ValidationSuccess(val email: Email): ValidationResult()
data class InvalidEmailAddress(val errorMessage: String): ValidationResult()
data class EmptySubject(val errorMessage: String): ValidationResult()
data class EmptyBody(val errorMessage: String): ValidationResult()

By informative I mean the caller of this method will easier/cleaner branch its code with when expression/statement. The same can be applied to send method. The more native Result for ::send can be like

sealed class SendResult
data class SendSuccess(val email: Email): SendResult()
data class IOFailure(val error: IOException): SendResult()
data class Timeout(val error: TimeoutException): SendResult()
data class UnAuthorized(val error: UnAuthorizedException): SendResult()

But in these cases we will lose our clean code(Not “The Clean Code”) to show what is actually going on. To solve it you may need to go and read Railway Oriented Programming in Kotlin again, where by using Railway Oriented Programming we actually want to clarify what the happy path of the proccess is. This implies chaining the Successes on the above results can bring us an almost similar clean code as before. But how?!


If we try to write the happy path in plain English it would be like

parse input
  - if failed return proper error messagevalidate
  - if there is an invalid email address return proper error message
  - if subject is empty return proper error message
  - if body is empty return proper error messagesend email
  - if there is a IO problem return proper error message
  - if there is a Timeout return proper error message
  - if unauthorized, return proper error message

Do you read it easily? Can you find the happy path at the first glance? It’s because of separation of the happy path from the failures by indenting the failure lines. That’s how our brain trained to read fast. If we could manage to write our code like this then the happy path will be clear for our brain at the first sight. We will call it a concise code. The first take away from the concise code above is that the successes define the happy path. The second one is that the success result is one and only one but the failures can be many. Also we just returned the proper error messages on all branches, but potentially you can do anything in those cases. The practical usecase can be replacing the failure with the default value where we will come back to it later.

Proposal

In the concise code above I see some kind of when expression, but it distinguishes the success result from the failures. It’s not an ordinary when expression in Kotlin. But Kotlin is a powerful language where supports DSLs. Maybe we can create a DSL to do this job for us.

For instance let’s try to create a DSL for ValidationResult. This DSL must returns the happy result and have something like when statement to branch the code for different failures. Something like

validate() elseIf {
  InvalidEmailAddress { errorMessage: String -> 
    /* handle failure */ 
  }
  EmptySubject { errorMessage: String -> /* handle failure */ }
  EmptyBody { errorMessage: String -> /* handle failure */ }
}

Note this DSL must use inline functions to be able to break the flow and do something like below with return

doWork() : Result<Email> {
  ...
  validate() elseIf {
    InvalidEmailAddress { errorMessage: String -> 
      return Failure(errorMessage)
    }
    EmptySubject { errorMessage: String -> /* handle failure */ }
    EmptyBody { errorMessage: String -> /* handle failure */ }
  }
  ...

To create this DSL we need a builder

public class ValidationResultElseIfBuilder(
  public val parent: ValidationResult
) {
  public lateinit var result: Success

  public inline fun InvalidEmailAddress(block: (errorMessage: String) -> Success): Unit {
    if(parent is InvalidEmailAddress) {
      result = block(parent.errorMessage)
    }
  }

  public inline fun EmptySubject(block: (errorMessage: String) -> Success): Unit {
    if(parent is EmptySubject) {
      result = block(parent.errorMessage)
    }
  }

  public inline fun EmptyBody(block: (errorMessage: String) -> Success): Unit {
    if(parent is EmptyBody) {
      result = block(parent.errorMessage)
    }
  }

Also we need to define the elseIf extension method like this

public inline infix fun ValidationResult.elseIf(build: ValidationResultElseIfBuilder.() -> Unit):
    Success {
  if (this is Success) {
    return this
  } else {
    val builder = ValidationResultElseIfBuilder(this)
    builder.build()
    return builder.result
  }
}

It’s a proof of concept DSL. Notice the result is lateinit which makes this DSL exhaustive on runtime, but unfortunately Kotlin DSL doesn’t have mandatory annotation or something like that, so we cannot force it on compile time :|

However, we can do the same for SendResult so will be able to call the send method like this

send() elseIf {
  IOFailure { error: IOException -> /* handle failure */ }
  Timeout { error: TimeoutException -> /* handle failure */ }
  UnAuthorized { error: UnAuthorizedException -> 
    /* handle failure */ 
  }
}

Then the doWork method will look like

fun doWork(): Result<Unit> {
  return input() into
    ::parse elseIf
    {
      return Failure("Cannot parse email")
    } into
    ::validate elseIf
    {
      InvalidEmailAddress { message -> return Failure(message) }
      EmptySubject { message -> return Failure(message) }
      EmptyBody { message -> return Failure(message) }
    } into
    ::send elseIf
    {
      IOFailure { error -> return Failure(error.message ?: "") }
      Timeout { error -> return Failure(error.message ?: "") }
      UnAuthorized { error -> return Failure(error.message ?: "") }
    } into
    { Success(Unit) }
}

fun parse(input: String): Result<Email> = ...

fun validate(input: Success<Email>): ValidationResult = ...

fun send(input: ValidateSuccess): SendResult = ...

where into is

inline infix fun <T, R> T.into(block: (T) -> R): R {
    return block(this)
}

which its definition is the same as let with infix modifier. It’s the best implementation that I came with where it arranges the failures to have an indent respect to the happy path, so probably it’s more readable to our brain.

Also did you notice the input of validate and send methods. They force the caller to finish the previous step successfully, which is fantastic. What do you think?


But who would pay for the boilertrap code the DSL needs! To solve this last problem, you can checkout Happy code generator. Also you can find above example with more details in the tests of this repository here.

More Complex Scenario

Let’s have a concise code like

parse input
  - if failed return proper error messagevalidate
  - if there is an invalid email address return proper error message
  - if subject is empty fix it by replacing the default subject
  - if body is empty return proper error messagesend email
  - if there is a IO problem return proper error message
  - if there is a Timeout return proper error message
  - if unauthorized, try to authorize then send it again.

As you can find out, there are two changes respect to the previous concise code. First in case of the empty subject we can fix the email, we will replace it with default subject, and the second one is the unauthorized failure can be fixed by trying to authorize and resend the email. In the end, we can use the above DSL and sealed classes before with a little bit of changes, but the concept is the same. So the code will be like

fun doWork(): Result<Unit> {
  return input() into
    ::parse elseIf
    {
      return Failure("Cannot parse email")
    } into
    ::validate elseIf
    {
      InvalidEmailAddress { message -> return Failure(message) }
      EmptySubject(::fixEmptySubject)
      EmptyBody { message -> return Failure(message) }
    } into
    ::send elseIf
    {
      IOFailure { error -> return Failure(error.message ?: "") }
      Timeout { error -> return Failure(error.message ?: "") }
      UnAuthorized { validatedEmail, _ ->
        validatedEmail into
          ::authorizeAndSend elseIf
          {
            return Failure(it)
          } into
          { SendSuccess(validatedEmail.email) }
      }
    } into
    { Success(Unit) }
}

Can you read it at the first glance? Again the failures are indented respect to happy path. By the way, you can find the complete code of this example in the previous repository but with different commit here.


Hope you enjoy coding more with this DSL. Don’t forget to upvote this post and please leave some feedbacks on comments. Thank you for your time!

Original Post

This article originally posted on ProAndroidDev.