Created
April 29, 2019 17:51
-
-
Save antonyharfield/1928d02a1163cf115d701deca5b99f63 to your computer and use it in GitHub Desktop.
Railway Oriented Programming in Kotlin (as described here)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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