kotlin: A functional gold mine

January 4, 2020

I found a great repository of functional Kotlin code examples. As part of writing and testing his Result monad, the author (Michael Bull) has translated the F# example presented in Scott Wlaschin's Railway Oriented Programming blog post.

I recently finished reading Scott's book "Domain Modeling Made Functional" (F# code from the book is here) and I highly recommend it for any one with a Java background that is interested in functional programming. The ideas are powerful

and all can all be implemented in Kotlin (and by extension, Java).

Data Pipeline

The andThen sugar comes with the Result monad written by Michael Bull.
    routing {
      get("/customers/{id}") {
          call.parameters.readId()
              .andThen(CustomerId.Companion::create)
              .andThen(CustomerService::getById)
              .mapError(::messageToResponse)
              .mapBoth(
                  success = { customer ->
                      call.respond(HttpStatusCode.OK, CustomerDto.from(customer))
                  },
                  failure = { (status, message) ->
                      call.respond(status, message)
                  }
              )
      }
  

Reference: example/Application.kt#L55-L69

Value Object

Using a companion create and the Result return type makes object creation plug in to a data flow; for example, the .andThen(CustomerId.Companion::create) in the above data pipeline example.
    package com.github.michaelbull.result.example.model.domain

    import com.github.michaelbull.result.Err
    import com.github.michaelbull.result.Ok
    import com.github.michaelbull.result.Result
    
    data class PersonalName(
        val first: String,
        val last: String
    ) {
        companion object {
            private const val MAX_LENGTH = 10
    
            fun create(first: String?, last: String?): Result {
                return when {
                    first.isNullOrBlank() -> Err(FirstNameRequired)
                    last.isNullOrBlank() -> Err(LastNameRequired)
                    first.length > MAX_LENGTH -> Err(FirstNameTooLong)
                    last.length > MAX_LENGTH -> Err(LastNameTooLong)
                    else -> Ok(PersonalName(first, last))
                }
            }
        }
    }
  

Reference: example/model/domain/PersonalName.kt

Type Lifting

Any errors creating a PersonalName (the above snippet), for example, LastNameTooLong, can be lifted into a DomainMessage type.
/**
 * All possible things that can happen in the use-cases
 */
sealed class DomainMessage

/* validation errors */

object CustomerRequired : DomainMessage()
object CustomerIdMustBePositive : DomainMessage()

object FirstNameRequired : DomainMessage()
object FirstNameTooLong : DomainMessage()

Reference: example/model/domain/DomainMessage.kt#L3-L14