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
|
package com.github.michaelbull.result
|
||||||
|
|
||||||
|
import com.github.michaelbull.result.coroutines.coroutineBinding
|
||||||
import kotlinx.benchmark.Benchmark
|
import kotlinx.benchmark.Benchmark
|
||||||
import kotlinx.benchmark.BenchmarkMode
|
import kotlinx.benchmark.BenchmarkMode
|
||||||
import kotlinx.benchmark.BenchmarkTimeUnit
|
import kotlinx.benchmark.BenchmarkTimeUnit
|
||||||
@ -11,12 +12,11 @@ import kotlinx.benchmark.State
|
|||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import com.github.michaelbull.result.coroutines.binding.binding as coroutineBinding
|
|
||||||
|
|
||||||
@State(Scope.Benchmark)
|
@State(Scope.Benchmark)
|
||||||
@BenchmarkMode(Mode.AverageTime)
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
@OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS)
|
@OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS)
|
||||||
class SuspendBindingBenchmark {
|
class CoroutineBindingBenchmark {
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
fun nonSuspendableBinding(blackhole: Blackhole) {
|
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
|
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 com.github.michaelbull.result.Result
|
||||||
import kotlinx.coroutines.CancellationException
|
import com.github.michaelbull.result.coroutines.CoroutineBindingScope
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import com.github.michaelbull.result.coroutines.coroutineBinding
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
@Deprecated(
|
||||||
* Suspending variant of [binding][com.github.michaelbull.result.binding].
|
message = "Use coroutineBinding instead",
|
||||||
* The suspendable [block] runs in a new [CoroutineScope], inheriting the parent [CoroutineContext].
|
replaceWith = ReplaceWith(
|
||||||
* This new scope is [cancelled][CoroutineScope.cancel] once a failing bind is encountered, eagerly cancelling all
|
expression = "coroutineBinding(block)",
|
||||||
* child [jobs][Job].
|
imports = ["com.github.michaelbull.result.coroutines.coroutineBinding"]
|
||||||
*/
|
)
|
||||||
|
)
|
||||||
public suspend inline fun <V, E> binding(crossinline block: suspend CoroutineBindingScope<E>.() -> V): Result<V, E> {
|
public suspend inline fun <V, E> binding(crossinline block: suspend CoroutineBindingScope<E>.() -> V): Result<V, E> {
|
||||||
contract {
|
return coroutineBinding(block)
|
||||||
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?)
|
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
message = "Use CoroutineBindingScope instead",
|
message = "Use CoroutineBindingScope instead",
|
||||||
replaceWith = ReplaceWith("CoroutineBindingScope<E>")
|
replaceWith = ReplaceWith("CoroutineBindingScope<E>")
|
||||||
)
|
)
|
||||||
public typealias SuspendableResultBinding<E> = 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.Err
|
||||||
import com.github.michaelbull.result.Ok
|
import com.github.michaelbull.result.Ok
|
||||||
@ -15,7 +15,7 @@ import kotlin.test.assertFalse
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
class AsyncSuspendableBindingTest {
|
class AsyncCoroutineBindingTest {
|
||||||
|
|
||||||
private sealed interface BindingError {
|
private sealed interface BindingError {
|
||||||
data object BindingErrorA : BindingError
|
data object BindingErrorA : BindingError
|
||||||
@ -34,7 +34,7 @@ class AsyncSuspendableBindingTest {
|
|||||||
return Ok(2)
|
return Ok(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: Result<Int, BindingError> = binding {
|
val result: Result<Int, BindingError> = coroutineBinding {
|
||||||
val x = async { provideX().bind() }
|
val x = async { provideX().bind() }
|
||||||
val y = async { provideY().bind() }
|
val y = async { provideY().bind() }
|
||||||
x.await() + y.await()
|
x.await() + y.await()
|
||||||
@ -63,7 +63,7 @@ class AsyncSuspendableBindingTest {
|
|||||||
return Err(BindingError.BindingErrorB)
|
return Err(BindingError.BindingErrorB)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: Result<Int, BindingError> = binding {
|
val result: Result<Int, BindingError> = coroutineBinding {
|
||||||
val x = async { provideX().bind() }
|
val x = async { provideX().bind() }
|
||||||
val y = async { provideY().bind() }
|
val y = async { provideY().bind() }
|
||||||
val z = async { provideZ().bind() }
|
val z = async { provideZ().bind() }
|
||||||
@ -96,7 +96,7 @@ class AsyncSuspendableBindingTest {
|
|||||||
val dispatcherA = StandardTestDispatcher(testScheduler)
|
val dispatcherA = StandardTestDispatcher(testScheduler)
|
||||||
val dispatcherB = 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 x = async(dispatcherA) { provideX().bind() }
|
||||||
val y = async(dispatcherB) { provideY().bind() }
|
val y = async(dispatcherB) { provideY().bind() }
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ class AsyncSuspendableBindingTest {
|
|||||||
val dispatcherB = StandardTestDispatcher(testScheduler)
|
val dispatcherB = StandardTestDispatcher(testScheduler)
|
||||||
val dispatcherC = StandardTestDispatcher(testScheduler)
|
val dispatcherC = StandardTestDispatcher(testScheduler)
|
||||||
|
|
||||||
val result: Result<Unit, BindingError> = binding {
|
val result: Result<Unit, BindingError> = coroutineBinding {
|
||||||
launch(dispatcherA) { provideX().bind() }
|
launch(dispatcherA) { provideX().bind() }
|
||||||
|
|
||||||
testScheduler.advanceTimeBy(20)
|
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.Err
|
||||||
import com.github.michaelbull.result.Ok
|
import com.github.michaelbull.result.Ok
|
||||||
@ -12,7 +12,7 @@ import kotlin.test.assertFalse
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
class SuspendableBindingTest {
|
class CoroutineBindingTest {
|
||||||
|
|
||||||
private object BindingError
|
private object BindingError
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ class SuspendableBindingTest {
|
|||||||
return Ok(2)
|
return Ok(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: Result<Int, BindingError> = binding {
|
val result: Result<Int, BindingError> = coroutineBinding {
|
||||||
val x = provideX().bind()
|
val x = provideX().bind()
|
||||||
val y = provideY().bind()
|
val y = provideY().bind()
|
||||||
x + y
|
x + y
|
||||||
@ -52,7 +52,7 @@ class SuspendableBindingTest {
|
|||||||
return Ok(x + 2)
|
return Ok(x + 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: Result<Int, BindingError> = binding {
|
val result: Result<Int, BindingError> = coroutineBinding {
|
||||||
val x = provideX().bind()
|
val x = provideX().bind()
|
||||||
val y = provideY(x.toInt()).bind()
|
val y = provideY(x.toInt()).bind()
|
||||||
y
|
y
|
||||||
@ -81,7 +81,7 @@ class SuspendableBindingTest {
|
|||||||
return Ok(2)
|
return Ok(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: Result<Int, BindingError> = binding {
|
val result: Result<Int, BindingError> = coroutineBinding {
|
||||||
val x = provideX().bind()
|
val x = provideX().bind()
|
||||||
val y = provideY().bind()
|
val y = provideY().bind()
|
||||||
val z = provideZ().bind()
|
val z = provideZ().bind()
|
||||||
@ -118,7 +118,7 @@ class SuspendableBindingTest {
|
|||||||
return Err(BindingError)
|
return Err(BindingError)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: Result<Int, BindingError> = binding {
|
val result: Result<Int, BindingError> = coroutineBinding {
|
||||||
val x = provideX().bind()
|
val x = provideX().bind()
|
||||||
val y = provideY().bind()
|
val y = provideY().bind()
|
||||||
val z = provideZ().bind()
|
val z = provideZ().bind()
|
||||||
@ -152,7 +152,7 @@ class SuspendableBindingTest {
|
|||||||
return Ok(2)
|
return Ok(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: Result<Int, BindingError> = binding {
|
val result: Result<Int, BindingError> = coroutineBinding {
|
||||||
val x = provideX().bind()
|
val x = provideX().bind()
|
||||||
val y = provideY().bind()
|
val y = provideY().bind()
|
||||||
val z = provideZ().bind()
|
val z = provideZ().bind()
|
Loading…
Reference in New Issue
Block a user