//arrow-fx-coroutines/arrow.fx.coroutines/Resource
common sealed class Resource<out A>
Resource models resource allocation and releasing. It is especially useful when multiple resources that depend on each other need to be acquired and later released in reverse order. Or when you want to load independent resources in parallel.
When a resource is created we can call use to run a suspend computation with the resource. The finalizers are then guaranteed to run afterwards in reverse order of acquisition.
Consider the following use case:
import arrow.fx.coroutines.*
class UserProcessor {
fun start(): Unit = println("Creating UserProcessor")
fun shutdown(): Unit = println("Shutting down UserProcessor")
fun process(ds: DataSource): List<String> =
ds.users().map { "Processed $it" }
}
class DataSource {
fun connect(): Unit = println("Connecting dataSource")
fun users(): List<String> = listOf("User-1", "User-2", "User-3")
fun close(): Unit = println("Closed dataSource")
}
class Service(val db: DataSource, val userProcessor: UserProcessor) {
suspend fun processData(): List<String> = throw RuntimeException("I'm going to leak resources by not closing them")
}
//sampleStart
suspend fun main(): Unit {
val userProcessor = UserProcessor().also { it.start() }
val dataSource = DataSource().also { it.connect() }
val service = Service(dataSource, userProcessor)
service.processData()
dataSource.close()
userProcessor.shutdown()
}
//sampleEnd
In the following example, we are creating and using a service that has a dependency on two resources: A database and a processor. All resources need to be closed in the correct order at the end. However this program is not safe because it is prone to leaking dataSource
and userProcessor
when an exception or cancellation signal occurs whilst using the service. As a consequence of the resource leak, this program does not guarantee the correct release of resources if something fails while acquiring or using the resource. Additionally manually keeping track of acquisition effects is an unnecessary overhead.
We can split the above program into 3 different steps:
That is exactly what Resource
does, and how we can solve our problem:
Creating a resource can be easily done by the resource
DSL, and there are two ways to define the finalizers with release
or releaseCase
.
import arrow.fx.coroutines.*
val resourceA = resource {
"A"
} release { a ->
println("Releasing $a")
}
val resourceB = resource {
"B"
} releaseCase { b, exitCase ->
println("Releasing $b with exit: $exitCase")
}
Here releaseCase
also signals with what ExitCase state the use
step finished.
import arrow.fx.coroutines.*
class UserProcessor {
fun start(): Unit = println("Creating UserProcessor")
fun shutdown(): Unit = println("Shutting down UserProcessor")
fun process(ds: DataSource): List<String> =
ds.users().map { "Processed $it" }
}
class DataSource {
fun connect(): Unit = println("Connecting dataSource")
fun users(): List<String> = listOf("User-1", "User-2", "User-3")
fun close(): Unit = println("Closed dataSource")
}
class Service(val db: DataSource, val userProcessor: UserProcessor) {
suspend fun processData(): List<String> = userProcessor.process(db)
}
//sampleStart
val userProcessor = resource {
UserProcessor().also(UserProcessor::start)
} release UserProcessor::shutdown
val dataSource = resource {
DataSource().also { it.connect() }
} release DataSource::close
suspend fun main(): Unit {
userProcessor.parZip(dataSource) { userProcessor, ds ->
Service(ds, userProcessor)
}.use { service -> service.processData() }
}
//sampleEnd
Resources are immutable and can be composed using zip or parZip. Resources guarantee that their release finalizers are always invoked in the correct order when an exception is raised or the context where the program is running gets canceled.
To achieve this Resource ensures that the acquire
&release
step are NonCancellable. If a cancellation signal, or an exception is received during acquire
, the resource is assumed to not have been acquired and thus will not trigger the release function. => Any composed resources that are already acquired they will be guaranteed to release as expected.
If you don’t need a data-type like Resource but want a function alternative to try/catch/finally
with automatic error composition, and automatic NonCancellableacquire
and release
steps use bracketCase or bracket.
Name | Summary |
---|---|
Allocate | common class Allocate<A>(val acquire: suspend () -> A, val release: suspend (A, ExitCase) -> Unit) : Resource<A> |
Bind | common |
Companion | common object Companion |
Defer | common |
Dsl | common data class Dsl<A>(val dsl: suspend ResourceScope.() -> A) : Resource<A> |
Name | Summary |
---|---|
ap | common fun <B> ap(ff: Resource<(A) -> B>): Resource<B> |
flatMap | common fun <B> flatMap(f: (A) -> Resource<B>): Resource<B> Create a resource value of B from a resource A by mapping f. |
map | common fun <B> map(f: suspend (A) -> B): Resource<B> |
parZip | common fun <B, C> parZip(fb: Resource<B>, f: suspend (A, B) -> C): Resource<C> common fun <B, C> parZip(ctx: CoroutineContext = Dispatchers.Default, fb: Resource<B>, f: suspend (A, B) -> C): Resource<C> Composes two Resources together by zipping them in parallel, by running both their acquire handlers in parallel, and both release handlers in parallel. |
tap | common fun tap(f: suspend (A) -> Unit): Resource<A> Useful for setting up/configuring an acquired resource |
use | common infix suspend tailrec fun <B> use(f: suspend (A) -> B): B Use the created resource When done will run all finalizers |
zip | common fun <B> zip(other: Resource<B>): Resource<Pair<A, B» inline fun <B, C> zip(other: Resource<B>, crossinline combine: (A, B) -> C): Resource<C> inline fun <B, C, D, E> zip(b: Resource<B>, c: Resource<C>, d: Resource<D>, crossinline map: (A, B, C, D) -> E): Resource<E> inline fun <B, C, D, E, F, G> zip(b: Resource<B>, c: Resource<C>, d: Resource<D>, e: Resource<E>, crossinline map: (A, B, C, D, E) -> G): Resource<G> inline fun <B, C, D, E, F, G, H> zip(b: Resource<B>, c: Resource<C>, d: Resource<D>, e: Resource<E>, f: Resource<F>, crossinline map: (A, B, C, D, E, F) -> G): Resource<G> inline fun <B, C, D, E, F, G, H> zip(b: Resource<B>, c: Resource<C>, d: Resource<D>, e: Resource<E>, f: Resource<F>, g: Resource<G>, crossinline map: (A, B, C, D, E, F, G) -> H): Resource<H> inline fun <B, C, D, E, F, G, H, I> zip(b: Resource<B>, c: Resource<C>, d: Resource<D>, e: Resource<E>, f: Resource<F>, g: Resource<G>, h: Resource<H>, crossinline map: (A, B, C, D, E, F, G, H) -> I): Resource<I> inline fun <B, C, D, E, F, G, H, I, J> zip(b: Resource<B>, c: Resource<C>, d: Resource<D>, e: Resource<E>, f: Resource<F>, g: Resource<G>, h: Resource<H>, i: Resource<I>, crossinline map: (A, B, C, D, E, F, G, H, I) -> J): Resource<J> inline fun <B, C, D, E, F, G, H, I, J, K> zip(b: Resource<B>, c: Resource<C>, d: Resource<D>, e: Resource<E>, f: Resource<F>, g: Resource<G>, h: Resource<H>, i: Resource<I>, j: Resource<J>, crossinline map: (A, B, C, D, E, F, G, H, I, J) -> K): Resource<K> common inline fun <B, C, D> zip(b: Resource<B>, c: Resource<C>, crossinline map: (A, B, C) -> D): Resource<D> Combines two independent resource values with the provided map function, returning the resulting immutable Resource value. The finalizers run in order of left to right by using flatMap under the hood, but zip provides a nicer syntax for combining values that don’t depend on each-other. |
Name |
---|
Bind |
Allocate |
Defer |
Dsl |
Name | Summary |
---|---|
asFlow | common fun <A> Resource<A>.asFlow(): runs [Resource.use](use.html) and emits [A](../as-flow.html) of the resource |
release | common infix fun <A> Resource<A>.release(release: suspend (A) -> Unit): Resource<A> Composes a release action to a Resource.use action creating a Resource. |
releaseCase | common infix fun <A> Resource<A>.releaseCase(release: suspend (A, ExitCase) -> Unit): Resource<A> Composes a releaseCase action to a Resource.use action creating a Resource. |
Do you like Arrow?
✖