Git Product home page Git Product logo

result's Introduction

Result

Kotlin Github Action MavenCentral Codecov

This is a tiny framework for modelling success/failure of operations in Kotlin. In short, it is a model in type of Result<V: Any?, E : Throwable>.

Ideology

Result<V: Any?, E: Throwable> is to provide higher abstraction of operation that can be ended with result either success or failure. Result.Success represents value in case of success, and Result.Failure represents error in case of failure which is upper bounded with Throwable type.

Installation

Gradle

repositories {
    mavenCentral()
}

dependencies {
    // if you are working on JVM or Android only project
    implementation("com.github.kittinunf.result:result-jvm:«version»") //for JVM support
    
    // if you are working in KMM project
    implementation("com.github.kittinunf.result:result:«version»") //for Kotlin Multiplatform support
}

TL;DR

This model is highly inspired by "Railway Oriented Programming" concept.

Result allows one to express series of success/failure operations in Kotlin as;

Result.of<T, Throwable>(doOperation())
      .flatMap { normalizedData(it) }
      .map { createRequestFromData(it) }
      .flatMap { database.updateFromRequest(it) }

Work with Result is easy

//multi-declaration
val (value, error) = result

//get
val value: Int = result.get() // throw exception if error

//terminal operator
//success
result.success {
}

//failure
result.failure {
}

//fold is there, if you want to handle both success and failure
result.fold({ value ->
    //do something with value
}, { err ->
    //do something with err
})

Why

Result is suitable whenever there is a need to represent an operation that has the possibility of failure. Error handling can be cumbersome to work with. Result helps process the operations in a nice, functional way, while maintaining readability to your code.

Let's consider a need to read data from foo, and to perform some further validation

fun process(): String {
    try {
        val foo = File("/path/to/file/foo.txt").readText()
        val isSuccessful = processData(foo)
        if (!isSuccessful) {
            return "Data is corrupted and cannot be processed"
        }
    } catch (e: Throwable) {
        //do something if error
        Logger.log(ERROR, e.message())
    }
}

However, things start getting ugly when we have chain of operations being run sequentially, such as

fun process(): String {
    try {
        val foo = File("/path/to/file/foo.txt").readText()
        val isSuccessful = normalizedData(foo)
        if (!isSuccessful) {
            return "Data cannot be processable"
        }
        val request = createRequestFromData(foo)
        try {
            val result = database.updateFromRequest(request)
            if (!result) {
                return "Record in DB is not found"
            }
        } catch (dbEx: DBThrowable) {
            return "DB error, cannot update"
        }
    } catch (e: Throwable) {
        //do something if error
        Logger.log(ERROR, e.message())
    }
}

Ouch, it looks pretty bleak.

Let's see how Result can help us.

First, we break things down into a small set of model in Result.

  • Read a file
val operation = { File("/path/to/file/foo.txt").readText() }
Result.of { operation() }  // Result<String, FileThrowable>
  • Normalize a data
fun normalizedData(foo): Result<Boolean, NormalizedThrowable> {
    Result.of { foo.normalize() }
}
  • Create a request from data
fun createRequestFromData(foo): Request {
    return createRequest(foo)
}
  • Update DB with Request
fun database.updateFromRequest(request): Result<Boolean, DBThrowable> {
    val transaction = request.transaction
    return Result.of { 
        db.openTransaction {
            val success = db.execute(transaction)
            if (!success) {
                throw DBThrowable("Error")
            }
            return success
        }
    }
}

The whole operation can be chained by the following;

Result.of { doOperation() }
      .flatMap { normalizedData(it) }
      .map { createRequestFromData(it) }
      .flatMap { database.updateFromRequest(it) }

The creates a nice "happy path" of the whole chain, also handle error as appropriate. It looks better and cleaner, right?.

Never Fail Operation

In some case, one wants to model an always successful operation. Result<V: Any?, NoException> is a good idea for that. NoException is to indicate that there is no exception to throw. E.g.

// Add operation can never be failure
fun add(i: Int, j: Int) : Result<Int, NoException>

Nice thing about modelling in this way is to be able to compose it with others "fail-able" operations in Result.

High Order functions

Success

map and flatMap

map transforms Result with given transformation (V) -> U. As a result, we are able to transform V into a new V in the case where Result is Result.Success. When Result is Result.Failure, error is re-wrapped into a new Result.

flatMap is similar to map, however it requires transformation in type of (V) -> Result<U, ...> .

Failure

mapError and flatMapError

mapError ((E) -> E2) and flatMapError ((E) -> Result<E2, ...>) are counterpart of map and flatMap. However, they are operate on Result.Failure. It is quite handy when one needs to do some transformation on given Throwable into a custom type of Throwable that suits ones' need.

More features

Please check out more features in the ResultTest

Railway Oriented Programming

If interested, here are more articles that one might enjoy.

Credit to Scott Wlaschin

Credits

Result is brought to you by contributors.

License

Result is released under the MIT license.

result's People

Contributors

kittinunf avatar globegitter avatar mplatvoet avatar pt2121 avatar dachelimo avatar lopes-felipe avatar commandertvis avatar pitagoras3 avatar yoavst avatar beyondeye avatar lukaszkalnik avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.