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

  • total functions,
  • writing code that will not represent an invalid state instead instead of writing tests,
  • I/O sandwiches,
  • baking dependencies in with partial application,
  • type lifting,
  • and data flow programming

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

Tags: functional kotlin