Skip to content

Instantly share code, notes, and snippets.

@antonyharfield
Created April 29, 2019 17:51
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save antonyharfield/1928d02a1163cf115d701deca5b99f63 to your computer and use it in GitHub Desktop.
Save antonyharfield/1928d02a1163cf115d701deca5b99f63 to your computer and use it in GitHub Desktop.
Railway Oriented Programming in Kotlin (as described here)
// Result is a superpowered enum that can be Success or Failure
// and the basis for a railway junction
sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val errorMessage: String): Result<T>()
// Composition: apply a function f to Success results
infix fun <T,U> Result<T>.then(f: (T) -> Result<U>) =
when (this) {
is Success -> f(this.value)
is Failure -> Failure(this.errorMessage)
}
// Pipe input: the beginning of a railway
infix fun <T,U> T.to(f: (T) -> Result<U>) = Success(this) then f
// Handle error output: the end of a railway
infix fun <T> Result<T>.otherwise(f: (String) -> Unit) =
if (this is Failure) f(this.errorMessage) else Unit
// An example email sending module that reads input, parses, validates, and sends
fun main(args: Array<String>) {
input() to
::parse then
::validate then
::send otherwise
::error
}
data class Email(
val to: String,
val subject: String,
val body: String
)
// Read in lines of input from the stdin
fun readLines(prompts: List<String>): List<String> =
prompts.map {
print("${it}: ")
readLine() ?: ""
}
fun input() = readLines(listOf("To", "Subject", "Body"))
// Parse the lines of input to an Email object
fun parse(inputs: List<String>): Result<Email> =
if (inputs.size == 3)
Success(Email(to = inputs[0], subject = inputs[1], body = inputs[2]))
else
Failure("Unexpected end of input")
// Validate the email address
fun validAddress(email: Email): Result<Email> =
if (email.to.contains("@"))
Success(email)
else
Failure("Invalid email address")
// Validate the subject and body are not blank
fun notBlank(email: Email): Result<Email> =
if (email.subject != "" && email.body != "")
Success(email)
else
Failure("Subject and body must not be blank")
// Composition of validation functions
fun validate(email: Email) = validAddress(email) then ::notBlank
// Send the email (typically this would have an unhappy path too)
fun send(email: Email): Result<Unit> {
println("Sent to ${email.to}. Whoosh!")
return Success(Unit)
}
// The error handler
fun error(message: String) =
println("Something went wrong: ${message}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment