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