arrow-fx-coroutines / arrow.fx.coroutines / Resource

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.

When a resource is created one can make use of use to run a 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.*

object Consumer
object Handle

class Service(val handle: Handle, val consumer: Consumer)

suspend fun createConsumer(): Consumer = Consumer.also { println("Creating consumer") }
suspend fun createDBHandle(): Handle = Handle.also { println("Creating db handle") }
suspend fun createFancyService(consumer: Consumer, handle: Handle): Service =
  Service(handle, consumer).also { println("Creating service") }

suspend fun closeConsumer(consumer: Consumer): Unit = println("Closed consumer")
suspend fun closeDBHandle(handle: Handle): Unit = println("Closed db handle")
suspend fun shutDownFancyService(service: Service): Unit = println("Closed service")

//sampleStart
val program = suspend {
  val consumer = createConsumer()
  val handle = createDBHandle()
  val service = createFancyService(consumer, handle)

  // use service
  // <...>

  // we are done, now onto releasing resources
  shutDownFancyService(service)
  closeDBHandle(handle)
  closeConsumer(consumer)
}
//sampleEnd
suspend fun main(): Unit = program.invoke()

Here we are creating and then using a service that has a dependency on two resources: A database handle and a consumer of some sort. All three resources need to be closed in the correct order at the end. However this program is quite bad! It does not guarantee release if something failed in between and keeping track of acquisition order is unnecessary overhead.

That is where Resource comes in:

import arrow.fx.coroutines.*

object Consumer
object Handle

class Service(val handle: Handle, val consumer: Consumer)

suspend fun createConsumer(): Consumer = Consumer.also { println("Creating consumer") }
suspend fun createDBHandle(): Handle = Handle.also { println("Creating db handle") }
suspend fun createFancyService(consumer: Consumer, handle: Handle): Service =
  Service(handle, consumer).also { println("Creating service") }

suspend fun closeConsumer(consumer: Consumer): Unit = println("Closed consumer")
suspend fun closeDBHandle(handle: Handle): Unit = println("Closed db handle")
suspend fun shutDownFancyService(service: Service): Unit = println("Closed service")

//sampleStart
val resourceProgram = suspend {
  Resource(::createConsumer, ::closeConsumer)
    .zip(Resource(::createDBHandle, ::closeDBHandle))
    .flatMap { (consumer, handle) ->
      Resource({ createFancyService(consumer, handle) }, { service -> shutDownFancyService(service) })
    }.use { service ->
      // use service
      // <...>
      Unit
    }
}
//sampleEnd

suspend fun main(): Unit = resourceProgram.invoke()

All three programs do exactly the same with varying levels of simplicity and overhead. Resource uses Bracket under the hood but provides a nicer monadic interface for creating and releasing resources in order, whereas bracket is great for one-off acquisitions but becomes more complex with nested resources.

Functions

ap fun <B> ap(ff: Resource<(A) -> B>): Resource<B>
flatMap Create a new resource B from a resource A by mapping f.fun <B> flatMap(f: (A) -> Resource<B>): Resource<B>
map fun <B> map(f: (A) -> B): Resource<B>
map2 fun <B, C> map2(other: Resource<B>, combine: (A, B) -> C): Resource<C>
use Use the created resource When done will run all finalizerssuspend infix fun <B> use(f: suspend (A) -> B): B
zip fun <B> zip(other: Resource<B>): Resource<Pair<A, B>>

Companion Object Functions

defer fun <A> defer(f: suspend () -> Resource<A>): Resource<A>
fromExecutor Creates a single threaded CoroutineContext as a Resource. Upon release an orderly shutdown of the ExecutorService takes place in which previously submitted tasks are executed, but no new tasks will be accepted.fun fromExecutor(f: suspend () -> ExecutorService): Resource<CoroutineContext>
invoke Construct a Resource from a allocating function acquire and a release function release.operator fun <A> invoke(acquire: suspend () -> A, release: suspend (A, ExitCase) -> Unit): Resource<A>
operator fun <A> invoke(acquire: suspend () -> A, release: suspend (A) -> Unit): Resource<A>
just Create a Resource from a pure value A.fun <A> just(r: A): Resource<A>
singleThreadContext Creates a single threaded CoroutineContext as a Resource. Upon release an orderly shutdown of the ExecutorService takes place in which previously submitted tasks are executed, but no new tasks will be accepted.fun singleThreadContext(name: String): Resource<CoroutineContext>
tailRecM fun <A, B> tailRecM(a: A, f: (A) -> Resource<Either<A, B>>): Resource<B>

Do you like Arrow?

Arrow Org
<