Deprecate suspending variant of "binding" in favour of "coroutineBinding"
This matches the internally-called function named coroutineScope, and helps consumers distinguish between the blocking variant that is otherwise only differing in package name. This should also help convey to readers that structured concurrency will occur within the block.
This commit is contained in:
parent
dd5c96f983
commit
b19894a08c
@ -1,5 +1,6 @@
|
||||
package com.github.michaelbull.result
|
||||
|
||||
import com.github.michaelbull.result.coroutines.coroutineBinding
|
||||
import kotlinx.benchmark.Benchmark
|
||||
import kotlinx.benchmark.BenchmarkMode
|
||||
import kotlinx.benchmark.BenchmarkTimeUnit
|
||||
@ -11,12 +12,11 @@ import kotlinx.benchmark.State
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import com.github.michaelbull.result.coroutines.binding.binding as coroutineBinding
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS)
|
||||
class SuspendBindingBenchmark {
|
||||
class CoroutineBindingBenchmark {
|
||||
|
||||
@Benchmark
|
||||
fun nonSuspendableBinding(blackhole: Blackhole) {
|
@ -0,0 +1,70 @@
|
||||
package com.github.michaelbull.result.coroutines
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
* Suspending variant of [binding][com.github.michaelbull.result.binding].
|
||||
* The suspendable [block] runs in a new [CoroutineScope], inheriting the parent [CoroutineContext].
|
||||
* This new scope is [cancelled][CoroutineScope.cancel] once a failing bind is encountered, eagerly cancelling all
|
||||
* child [jobs][Job].
|
||||
*/
|
||||
public suspend inline fun <V, E> coroutineBinding(crossinline block: suspend CoroutineBindingScope<E>.() -> V): Result<V, E> {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
|
||||
lateinit var receiver: CoroutineBindingScopeImpl<E>
|
||||
|
||||
return try {
|
||||
coroutineScope {
|
||||
receiver = CoroutineBindingScopeImpl(this)
|
||||
|
||||
with(receiver) {
|
||||
Ok(block())
|
||||
}
|
||||
}
|
||||
} catch (ex: BindCancellationException) {
|
||||
receiver.result!!
|
||||
}
|
||||
}
|
||||
|
||||
internal object BindCancellationException : CancellationException(null as String?)
|
||||
|
||||
public interface CoroutineBindingScope<E> : CoroutineScope {
|
||||
public suspend fun <V> Result<V, E>.bind(): V
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal class CoroutineBindingScopeImpl<E>(
|
||||
delegate: CoroutineScope,
|
||||
) : CoroutineBindingScope<E>, CoroutineScope by delegate {
|
||||
|
||||
private val mutex = Mutex()
|
||||
var result: Result<Nothing, E>? = null
|
||||
|
||||
override suspend fun <V> Result<V, E>.bind(): V {
|
||||
return when (this) {
|
||||
is Ok -> value
|
||||
is Err -> mutex.withLock {
|
||||
if (result == null) {
|
||||
result = this
|
||||
coroutineContext.cancel(BindCancellationException)
|
||||
}
|
||||
|
||||
throw BindCancellationException
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +1,22 @@
|
||||
package com.github.michaelbull.result.coroutines.binding
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import com.github.michaelbull.result.coroutines.CoroutineBindingScope
|
||||
import com.github.michaelbull.result.coroutines.coroutineBinding
|
||||
|
||||
/**
|
||||
* Suspending variant of [binding][com.github.michaelbull.result.binding].
|
||||
* The suspendable [block] runs in a new [CoroutineScope], inheriting the parent [CoroutineContext].
|
||||
* This new scope is [cancelled][CoroutineScope.cancel] once a failing bind is encountered, eagerly cancelling all
|
||||
* child [jobs][Job].
|
||||
*/
|
||||
@Deprecated(
|
||||
message = "Use coroutineBinding instead",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "coroutineBinding(block)",
|
||||
imports = ["com.github.michaelbull.result.coroutines.coroutineBinding"]
|
||||
)
|
||||
)
|
||||
public suspend inline fun <V, E> binding(crossinline block: suspend CoroutineBindingScope<E>.() -> V): Result<V, E> {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
return coroutineBinding(block)
|
||||
}
|
||||
|
||||
lateinit var receiver: CoroutineBindingScopeImpl<E>
|
||||
|
||||
return try {
|
||||
coroutineScope {
|
||||
receiver = CoroutineBindingScopeImpl(this)
|
||||
|
||||
with(receiver) {
|
||||
Ok(block())
|
||||
}
|
||||
}
|
||||
} catch (ex: BindCancellationException) {
|
||||
receiver.result!!
|
||||
}
|
||||
}
|
||||
|
||||
internal object BindCancellationException : CancellationException(null as String?)
|
||||
|
||||
@Deprecated(
|
||||
message = "Use CoroutineBindingScope instead",
|
||||
replaceWith = ReplaceWith("CoroutineBindingScope<E>")
|
||||
)
|
||||
public typealias SuspendableResultBinding<E> = CoroutineBindingScope<E>
|
||||
|
||||
public interface CoroutineBindingScope<E> : CoroutineScope {
|
||||
public suspend fun <V> Result<V, E>.bind(): V
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal class CoroutineBindingScopeImpl<E>(
|
||||
delegate: CoroutineScope,
|
||||
) : CoroutineBindingScope<E>, CoroutineScope by delegate {
|
||||
|
||||
private val mutex = Mutex()
|
||||
var result: Result<Nothing, E>? = null
|
||||
|
||||
override suspend fun <V> Result<V, E>.bind(): V {
|
||||
return when (this) {
|
||||
is Ok -> value
|
||||
is Err -> mutex.withLock {
|
||||
if (result == null) {
|
||||
result = this
|
||||
coroutineContext.cancel(BindCancellationException)
|
||||
}
|
||||
|
||||
throw BindCancellationException
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.github.michaelbull.result.coroutines.binding
|
||||
package com.github.michaelbull.result.coroutines
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
@ -15,7 +15,7 @@ import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class AsyncSuspendableBindingTest {
|
||||
class AsyncCoroutineBindingTest {
|
||||
|
||||
private sealed interface BindingError {
|
||||
data object BindingErrorA : BindingError
|
||||
@ -34,7 +34,7 @@ class AsyncSuspendableBindingTest {
|
||||
return Ok(2)
|
||||
}
|
||||
|
||||
val result: Result<Int, BindingError> = binding {
|
||||
val result: Result<Int, BindingError> = coroutineBinding {
|
||||
val x = async { provideX().bind() }
|
||||
val y = async { provideY().bind() }
|
||||
x.await() + y.await()
|
||||
@ -63,7 +63,7 @@ class AsyncSuspendableBindingTest {
|
||||
return Err(BindingError.BindingErrorB)
|
||||
}
|
||||
|
||||
val result: Result<Int, BindingError> = binding {
|
||||
val result: Result<Int, BindingError> = coroutineBinding {
|
||||
val x = async { provideX().bind() }
|
||||
val y = async { provideY().bind() }
|
||||
val z = async { provideZ().bind() }
|
||||
@ -96,7 +96,7 @@ class AsyncSuspendableBindingTest {
|
||||
val dispatcherA = StandardTestDispatcher(testScheduler)
|
||||
val dispatcherB = StandardTestDispatcher(testScheduler)
|
||||
|
||||
val result: Result<Int, BindingError> = binding {
|
||||
val result: Result<Int, BindingError> = coroutineBinding {
|
||||
val x = async(dispatcherA) { provideX().bind() }
|
||||
val y = async(dispatcherB) { provideY().bind() }
|
||||
|
||||
@ -143,7 +143,7 @@ class AsyncSuspendableBindingTest {
|
||||
val dispatcherB = StandardTestDispatcher(testScheduler)
|
||||
val dispatcherC = StandardTestDispatcher(testScheduler)
|
||||
|
||||
val result: Result<Unit, BindingError> = binding {
|
||||
val result: Result<Unit, BindingError> = coroutineBinding {
|
||||
launch(dispatcherA) { provideX().bind() }
|
||||
|
||||
testScheduler.advanceTimeBy(20)
|
@ -1,4 +1,4 @@
|
||||
package com.github.michaelbull.result.coroutines.binding
|
||||
package com.github.michaelbull.result.coroutines
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
@ -12,7 +12,7 @@ import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class SuspendableBindingTest {
|
||||
class CoroutineBindingTest {
|
||||
|
||||
private object BindingError
|
||||
|
||||
@ -28,7 +28,7 @@ class SuspendableBindingTest {
|
||||
return Ok(2)
|
||||
}
|
||||
|
||||
val result: Result<Int, BindingError> = binding {
|
||||
val result: Result<Int, BindingError> = coroutineBinding {
|
||||
val x = provideX().bind()
|
||||
val y = provideY().bind()
|
||||
x + y
|
||||
@ -52,7 +52,7 @@ class SuspendableBindingTest {
|
||||
return Ok(x + 2)
|
||||
}
|
||||
|
||||
val result: Result<Int, BindingError> = binding {
|
||||
val result: Result<Int, BindingError> = coroutineBinding {
|
||||
val x = provideX().bind()
|
||||
val y = provideY(x.toInt()).bind()
|
||||
y
|
||||
@ -81,7 +81,7 @@ class SuspendableBindingTest {
|
||||
return Ok(2)
|
||||
}
|
||||
|
||||
val result: Result<Int, BindingError> = binding {
|
||||
val result: Result<Int, BindingError> = coroutineBinding {
|
||||
val x = provideX().bind()
|
||||
val y = provideY().bind()
|
||||
val z = provideZ().bind()
|
||||
@ -118,7 +118,7 @@ class SuspendableBindingTest {
|
||||
return Err(BindingError)
|
||||
}
|
||||
|
||||
val result: Result<Int, BindingError> = binding {
|
||||
val result: Result<Int, BindingError> = coroutineBinding {
|
||||
val x = provideX().bind()
|
||||
val y = provideY().bind()
|
||||
val z = provideZ().bind()
|
||||
@ -152,7 +152,7 @@ class SuspendableBindingTest {
|
||||
return Ok(2)
|
||||
}
|
||||
|
||||
val result: Result<Int, BindingError> = binding {
|
||||
val result: Result<Int, BindingError> = coroutineBinding {
|
||||
val x = provideX().bind()
|
||||
val y = provideY().bind()
|
||||
val z = provideZ().bind()
|
Loading…
Reference in New Issue
Block a user