Skip to content

Instantly share code, notes, and snippets.

@antonyharfield
Created April 29, 2019 17:51
Show Gist options
  • Select an option

  • Save antonyharfield/1928d02a1163cf115d701deca5b99f63 to your computer and use it in GitHub Desktop.

Select an option

Save antonyharfield/1928d02a1163cf115d701deca5b99f63 to your computer and use it in GitHub Desktop.

Revisions

  1. antonyharfield created this gist Apr 29, 2019.
    77 changes: 77 additions & 0 deletions RailwayOP-CompleteExample.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,77 @@
    // 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}")