Add monad comprehensions via binding block
This commit is contained in:
parent
1000c588c0
commit
9bcaa974ca
58
src/main/kotlin/com/github/michaelbull/result/Binding.kt
Normal file
58
src/main/kotlin/com/github/michaelbull/result/Binding.kt
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package com.github.michaelbull.result
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the specified function [block] with [ResultBinding] as its receiver and returns its [Result].
|
||||||
|
*
|
||||||
|
* When inside a [binding] block, the [bind][ResultBinding.bind] function is accessible on any [Result]. Calling the
|
||||||
|
* [bind][ResultBinding.bind] function will attempt to unwrap the [Result] and locally return its [value][Ok.value]. If
|
||||||
|
* the [Result] is an [Err], the binding block will terminate early and return the first [error][Err.error].
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```
|
||||||
|
* fun provideX(): Result<Int, ExampleErr> { ... }
|
||||||
|
* fun provideY(): Result<Int, ExampleErr> { ... }
|
||||||
|
*
|
||||||
|
* val result: Result<Int, ExampleErr> = binding {
|
||||||
|
* val x = provideX().bind()
|
||||||
|
* val y = provideY().bind()
|
||||||
|
* x + y
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @sample com.github.michaelbull.result.bind.ResultBindingTest
|
||||||
|
*/
|
||||||
|
inline fun <V, E> binding(crossinline block: ResultBinding<E>.() -> V): Result<V, E> {
|
||||||
|
val receiver = ResultBindingImpl<E>()
|
||||||
|
|
||||||
|
return try {
|
||||||
|
with(receiver) { Ok(block()) }
|
||||||
|
} catch (ex: BindException) {
|
||||||
|
receiver.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object BindException : Exception() {
|
||||||
|
override fun fillInStackTrace(): Throwable {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResultBinding<E> {
|
||||||
|
fun <V> Result<V, E>.bind(): V
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal class ResultBindingImpl<E> : ResultBinding<E> {
|
||||||
|
|
||||||
|
lateinit var error: Err<E>
|
||||||
|
|
||||||
|
override fun <V> Result<V, E>.bind(): V {
|
||||||
|
return when (this) {
|
||||||
|
is Ok -> value
|
||||||
|
is Err -> {
|
||||||
|
this@ResultBindingImpl.error = this
|
||||||
|
throw BindException
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
src/test/kotlin/com/github/michaelbull/result/BindingTest.kt
Normal file
74
src/test/kotlin/com/github/michaelbull/result/BindingTest.kt
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package com.github.michaelbull.result
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class BindingTest {
|
||||||
|
|
||||||
|
object BindingError
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun returnsOkIfAllBindsSuccessful() {
|
||||||
|
fun provideX(): Result<Int, BindingError> = Ok(1)
|
||||||
|
fun provideY(): Result<Int, BindingError> = Ok(2)
|
||||||
|
|
||||||
|
val result = binding<Int, BindingError> {
|
||||||
|
val x = provideX().bind()
|
||||||
|
val y = provideY().bind()
|
||||||
|
x + y
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(result is Ok)
|
||||||
|
assertEquals(3, result.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun returnsOkIfAllBindsOfDifferentTypeAreSuccessful() {
|
||||||
|
fun provideX(): Result<String, BindingError> = Ok("1")
|
||||||
|
fun provideY(x: Int): Result<Int, BindingError> = Ok(x + 2)
|
||||||
|
|
||||||
|
val result = binding<Int, BindingError> {
|
||||||
|
val x = provideX().bind()
|
||||||
|
val y = provideY(x.toInt()).bind()
|
||||||
|
y
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(result is Ok)
|
||||||
|
assertEquals(3, result.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun returnsFirstErrIfBindingFailed() {
|
||||||
|
fun provideX(): Result<Int, BindingError> = Ok(1)
|
||||||
|
fun provideY(): Result<Int, BindingError> = Err(BindingError)
|
||||||
|
fun provideZ(): Result<Int, BindingError> = Ok(2)
|
||||||
|
|
||||||
|
val result = binding<Int, BindingError> {
|
||||||
|
val x = provideX().bind()
|
||||||
|
val y = provideY().bind()
|
||||||
|
val z = provideZ().bind()
|
||||||
|
x + y + z
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(result is Err)
|
||||||
|
assertEquals(BindingError, result.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun returnsFirstErrIfBindingsOfDifferentTypesFailed() {
|
||||||
|
fun provideX(): Result<Int, BindingError> = Ok(1)
|
||||||
|
fun provideY(): Result<String, BindingError> = Err(BindingError)
|
||||||
|
fun provideZ(): Result<Int, BindingError> = Ok(2)
|
||||||
|
|
||||||
|
val result = binding<Int, BindingError> {
|
||||||
|
val x = provideX().bind()
|
||||||
|
val y = provideY().bind()
|
||||||
|
val z = provideZ().bind()
|
||||||
|
x + y.toInt() + z
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(result is Err)
|
||||||
|
assertEquals(BindingError, result.error)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user