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
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
.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
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