Skip to content

Commit

Permalink
[Documentation] Update Readme file (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
kittinunf committed Jul 15, 2021
1 parent a0624ca commit 5d9180b
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 55 deletions.
118 changes: 64 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,49 @@
# Result

[![Kotlin](https://img.shields.io/badge/kotlin-1.4.0-blue.svg)](http:https://kotlinlang.org)
[![jcenter](https://api.bintray.com/packages/kittinunf/maven/Result/images/download.svg)](https://bintray.com/kittinunf/maven/Result/_latestVersion)
[![Kotlin](https://img.shields.io/badge/kotlin-1.5.20-blue.svg)](http:https://kotlinlang.org)
![Github Action](https://github.com/kittinunf/Result/actions/workflows/Main.yml/badge.svg)
[![MavenCentral](https://maven-badges.herokuapp.com/maven-central/com.github.kittinunf.result/result/badge.svg)](https://search.maven.org/search?q=g:com.github.kittinunf.result)
[![Build Status](https://travis-ci.org/kittinunf/Result.svg?branch=master)](https://travis-ci.org/kittinunf/Result)
[![Codecov](https://codecov.io/github/kittinunf/Result/coverage.svg?branch=master)](https://codecov.io/gh/kittinunf/Result)

This is a tiny framework for modelling success/failure of operations in [Kotlin](http:https://kotlinlang.org). In short, it is a model in type of `Result<V: Any?, E : Exception>`.
This is a tiny framework for modelling success/failure of operations
in [Kotlin](http:https://kotlinlang.org). In short, it is a model in type
of `Result<V: Any?, E : Throwable>`.

## Ideology

`Result<V: Any?, E: Exception>` 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 `Exception` type.
`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

``` Groovy
repositories {
jcenter() //or mavenCentral()
mavenCentral()
}
dependencies {
implementation 'com.github.kittinunf.result:result:<latest-version>' //for jvm
implementation 'com.github.kittinunf.result:result-coroutines:<latest-version>' //for kotlin's coroutine support
// 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](http:https://fsharpforfunandprofit.com/rop/#monads)" concept.
This model is highly inspired
by "[Railway Oriented Programming](http:https://fsharpforfunandprofit.com/rop/#monads)" concept.

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

``` Kotlin
Result.of(operation)
Result.of<T, Throwable>(doOperation())
.flatMap { normalizedData(it) }
.map { createRequestFromData(it) }
.flatMap { database.updateFromRequest(it) }
Expand All @@ -48,9 +56,9 @@ Work with `Result` is easy
val (value, error) = result

//get
val value: Int = result.get<Int>() ?: 0
val ex: Exception = result.get<Exception>()!!
val value: Int = result.get() // throw exception if error

//terminal operator
//success
result.success {
}
Expand All @@ -62,28 +70,17 @@ result.failure {
//fold is there, if you want to handle both success and failure
result.fold({ value ->
//do something with value
}, { error ->
//do something with error
}, { err ->
//do something with err
})
```

Combine several results in a validation (without stopping at the first error)

``` Kotlin
val r1: Result<Int, Exception> = Result.of(1)
val r2: Result<Int, Exception> = Result.of{throw Exception("Not a number")}
val r3: Result<Int, Exception> = Result.of(3)
val r4: Result<Int, Exception> = Result.of{throw Exception("Division by zero")}

val validation = Validation(r1, r2, r3, r4)
validation.hasFailure //true
validation.failures.map{it.message} //[Not a number, Division by zero]
```

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

Expand All @@ -95,7 +92,7 @@ fun process(): String {
if (!isSuccessful) {
return "Data is corrupted and cannot be processed"
}
} catch (e: Exception) {
} catch (e: Throwable) {
//do something if error
Logger.log(ERROR, e.message())
}
Expand All @@ -118,10 +115,10 @@ fun process(): String {
if (!result) {
return "Record in DB is not found"
}
} catch (dbEx: DBException) {
} catch (dbEx: DBThrowable) {
return "DB error, cannot update"
}
} catch (e: Exception) {
} catch (e: Throwable) {
//do something if error
Logger.log(ERROR, e.message())
}
Expand All @@ -138,80 +135,93 @@ First, we break things down into a small set of model in `Result`.

``` Kotlin
val operation = { File("/path/to/file/foo.txt").readText() }
Result.of(operation) // Result<String, FileException>
Result.of { operation() } // Result<String, FileThrowable>
```

* Normalize a data

``` Kotlin
fun normalizedData(foo): Result<Boolean, NormalizedException> {
Result.of(foo.normalize())
fun normalizedData(foo): Result<Boolean, NormalizedThrowable> {
Result.of { foo.normalize() }
}
```

* Create a request from data

``` Kotlin
fun createRequestFromData(foo): Request {
return createRequest(foo)
}
```

* Update DB with Request

``` Kotlin
fun database.updateFromRequest(request): Result<Boolean, DBException> {
fun database.updateFromRequest(request): Result<Boolean, DBThrowable> {
val transaction = request.transaction
return Result.of(db.openTransaction {
val success = db.execute(transaction)
if (!success) {
throw DBException("Error")
return Result.of {
db.openTransaction {
val success = db.execute(transaction)
if (!success) {
throw DBThrowable("Error")
}
return success
}
return success
})
}
}
```

The whole operation can be chained by the following;

``` Kotlin
Result.of(operation)
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?.
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.
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.

``` Kotlin
// 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 "failable" operations in `Result`.
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`.
`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, ...>`.
`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 `Exception` into a custom type of `Exception` that suits ones' need.
`mapError` and `flatMapError`

## Support for Kotlin's Coroutines
`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.

### SuspendableResult & SuspendableValidation
## More features

These classes are an exact copy of the `Result` and `Validation` classes respectively. Use these classes if you are planning on using coroutines in your functions.
Please check out more features in
the [ResultTest](./result/src/commonTest/kotlin/com/github/kittinunf/result/ResultTest.kt)

## Railway Oriented Programming

Expand Down
4 changes: 3 additions & 1 deletion result/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ import java.io.IOException

plugins {
kotlin("multiplatform")

java
jacoco

id("publication")
}

val artifactGroupId: String by project
group = artifactGroupId

val gitSha = "git rev-parse --short HEAD".runCommand(project.rootDir)?.trim().orEmpty()
val artifactPublishVersion: String by project

val isReleaseBuild: Boolean
get() = properties.containsKey("release")

val artifactPublishVersion: String by project
version = if (isReleaseBuild) artifactPublishVersion else "master-$gitSha-SNAPSHOT"

kotlin {
Expand Down

0 comments on commit 5d9180b

Please sign in to comment.