Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/kittinunf/result
The modelling for success/failure of operations in Kotlin and KMM (Kotlin Multiplatform Mobile)
https://github.com/kittinunf/result
functional kotlin kotlin-multiplatform kotlin-multiplatform-library optional result
Last synced: 3 days ago
JSON representation
The modelling for success/failure of operations in Kotlin and KMM (Kotlin Multiplatform Mobile)
- Host: GitHub
- URL: https://github.com/kittinunf/result
- Owner: kittinunf
- License: other
- Created: 2015-10-28T12:34:53.000Z (about 9 years ago)
- Default Branch: master
- Last Pushed: 2024-07-19T09:08:27.000Z (5 months ago)
- Last Synced: 2024-12-13T13:02:00.732Z (10 days ago)
- Topics: functional, kotlin, kotlin-multiplatform, kotlin-multiplatform-library, optional, result
- Language: Kotlin
- Homepage:
- Size: 605 KB
- Stars: 920
- Watchers: 16
- Forks: 54
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
README
# Result
[![Kotlin](https://img.shields.io/badge/kotlin-1.9.20-blue.svg)](http://kotlinlang.org)
[![MavenCentral](https://maven-badges.herokuapp.com/maven-central/com.github.kittinunf.result/result-jvm/badge.svg)](https://search.maven.org/search?q=g:com.github.kittinunf.result)
![Run Gradle](https://github.com/kittinunf/Result/workflows/Run%20Gradle%20on%20Main/badge.svg?branch=master)
[![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://kotlinlang.org). In short, it is a model in type
of `Result`.## Ideology
`Result` 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 {
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](http://fsharpforfunandprofit.com/rop/#monads)" concept.`Result` allows one to express series of success/failure operations in Kotlin as;
``` Kotlin
Result.of(doOperation())
.flatMap { normalizedData(it) }
.map { createRequestFromData(it) }
.flatMap { database.updateFromRequest(it) }
```Work with `Result` is easy
``` Kotlin
//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
``` Kotlin
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
``` Kotlin
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
``` Kotlin
val operation = { File("/path/to/file/foo.txt").readText() }
Result.of { operation() } // Result
```* Normalize a data
``` Kotlin
fun normalizedData(foo): Result {
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 {
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;
``` Kotlin
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` 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
```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`
.### Failure
`mapError` and `flatMapError`
`mapError` (`(E) -> E2`) and `flatMapError` (`(E) -> Result`) 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](./result/src/commonTest/kotlin/com/github/kittinunf/result/ResultTest.kt)## Railway Oriented Programming
If interested, here are more articles that one might enjoy.
* [Recipe Part 2](http://fsharpforfunandprofit.com/posts/recipe-part2/)
* [Approach Error Handing](https://speakerdeck.com/swlaschin/railway-oriented-programming-a-functional-approach-to-error-handling)
* [Example](https://github.com/swlaschin/Railway-Oriented-Programming-Example)Credit to Scott Wlaschin
## Credits
Result is brought to you by [contributors](https://github.com/kittinunf/Result/graphs/contributors).
## License
Result is released under the [MIT](http://opensource.org/licenses/MIT) license.