Resource

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")
}

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()
}

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 leak 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:

  1. Acquiring the resource

  2. Using the resource

  3. Releasing the resource with either ExitCase.Completed, ExitCase.Failure or ExitCase.Cancelled.

That is exactly what Resource does, and how we can solve our problem:

Constructing Resource

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.

Using and composing Resource

Arrow offers the same elegant bind DSL for Resource as you might be familiar with from Arrow Core.

import arrow.fx.coroutines.*
import arrow.fx.coroutines.continuations.resource

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)
}

val userProcessor = resource {
UserProcessor().also(UserProcessor::start)
} release UserProcessor::shutdown

val dataSource = resource {
DataSource().also { it.connect() }
} release DataSource::close

suspend fun main(): Unit {
resource {
parZip({ userProcessor.bind() }, { dataSource.bind() }) { userProcessor, ds ->
Service(ds, userProcessor)
}
}.use { service -> service.processData() }
}

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.

Types

Link copied to clipboard
class Allocate<A>(val acquire: suspend () -> A, val release: suspend (A, ExitCase) -> Unit) : Resource<A>
Link copied to clipboard
class Bind<A, B>(val source: Resource<A>, val f: (A) -> Resource<B>) : Resource<B>
Link copied to clipboard
object Companion
Link copied to clipboard
class Defer<A>(val resource: suspend () -> Resource<A>) : Resource<A>
Link copied to clipboard
data class Dsl<A>(val dsl: suspend ResourceScope.() -> A) : Resource<A>

Functions

Link copied to clipboard
@DelicateCoroutinesApi
suspend fun allocated(): Pair<suspend () -> A, suspend (@UnsafeVariance A, ExitCase) -> Unit>

Deconstruct Resource into an acquire and release handlers. The release action must always be called with resource A returned from acquire, if the release step is never called, then the resource A will leak. The acquire and release steps are already made NonCancellable to guarantee correct invocation like Resource or bracketCase.

Link copied to clipboard
fun <B> ap(ff: Resource<(A) -> B>): Resource<B>
Link copied to clipboard
fun <B> flatMap(f: (A) -> Resource<B>): Resource<B>

Create a resource value of B from a resource A by mapping f.

Link copied to clipboard
fun <B> map(f: suspend (A) -> B): Resource<B>
Link copied to clipboard
fun <B, C> parZip(fb: Resource<B>, f: suspend (A, B) -> C): Resource<C>

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.

Link copied to clipboard
fun tap(f: suspend (A) -> Unit): Resource<A>

Useful for setting up/configuring an acquired resource

Link copied to clipboard
infix suspend tailrec fun <B> use(f: suspend (A) -> B): B

Use the created resource When done will run all finalizers

Link copied to clipboard
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, 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>

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.

Inheritors

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Extensions

Link copied to clipboard
fun <A> Resource<A>.asFlow(): Flow<A>

Runs Resource.use and emits A of the resource

Link copied to clipboard
infix fun <A> Resource<A>.release(release: suspend (A) -> Unit): Resource<A>

Composes a release action to a Resource.use action creating a Resource.

Link copied to clipboard
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.