arrow-fx / arrow.fx / Resource
sealed class ~~Resource~~<F, E, A> :
ResourceOf
<F, E, A>
Deprecated: The IO datatype and it’s related type classes will disappear in Arrow 0.13.0. All useful operations are offered directly over suspend functions by Arrow Fx Coroutines. https://arrow-kt.io/docs/fx/async/
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.IO
import arrow.fx.extensions.fx
object Consumer
object Handle
class Service(val handle: Handle, val consumer: Consumer)
fun createConsumer(): IO<Consumer> = IO { println("Creating consumer"); Consumer }
fun createDBHandle(): IO<Handle> = IO { println("Creating db handle"); Handle }
fun createFancyService(consumer: Consumer, handle: Handle): IO<Service> = IO { println("Creating service"); Service(handle, consumer) }
fun closeConsumer(consumer: Consumer): IO<Unit> = IO { println("Closed consumer") }
fun closeDBHandle(handle: Handle): IO<Unit> = IO { println("Closed db handle") }
fun shutDownFancyService(service: Service): IO<Unit> = IO { println("Closed service") }
//sampleStart
val program = IO.fx {
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
fun main() {
program.unsafeRunSync()
}
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.
There is already a typeclass called bracket that we can use to make our life easier:
import arrow.fx.IO
import arrow.fx.extensions.io.bracket.bracket
object Consumer
object Handle
class Service(val handle: Handle, val consumer: Consumer)
fun createConsumer(): IO<Consumer> = IO { println("Creating consumer"); Consumer }
fun createDBHandle(): IO<Handle> = IO { println("Creating db handle"); Handle }
fun createFancyService(consumer: Consumer, handle: Handle): IO<Service> = IO { println("Creating service"); Service(handle, consumer) }
fun closeConsumer(consumer: Consumer): IO<Unit> = IO { println("Closed consumer") }
fun closeDBHandle(handle: Handle): IO<Unit> = IO { println("Closed db handle") }
fun shutDownFancyService(service: Service): IO<Unit> = IO { println("Closed service") }
//sampleStart
val bracketProgram =
createConsumer().bracket(::closeConsumer) { consumer ->
createDBHandle().bracket(::closeDBHandle) { handle ->
createFancyService(consumer, handle).bracket(::shutDownFancyService) { service ->
// use service
// <...>
IO.unit
}
}
}
//sampleEnd
fun main() {
bracketProgram.unsafeRunSync()
}
This is already much better. Now our services are guaranteed to close properly and also in order. However this pattern gets worse and worse the more resources you add because you need to nest deeper and deeper.
That is where Resource
comes in:
import arrow.fx.IO
import arrow.fx.Resource
import arrow.fx.extensions.resource.monad.monad
import arrow.fx.extensions.io.bracket.bracket
import arrow.fx.fix
object Consumer
object Handle
class Service(val handle: Handle, val consumer: Consumer)
fun createConsumer(): IO<Consumer> = IO { println("Creating consumer"); Consumer }
fun createDBHandle(): IO<Handle> = IO { println("Creating db handle"); Handle }
fun createFancyService(consumer: Consumer, handle: Handle): IO<Service> = IO { println("Creating service"); Service(handle, consumer) }
fun closeConsumer(consumer: Consumer): IO<Unit> = IO { println("Closed consumer") }
fun closeDBHandle(handle: Handle): IO<Unit> = IO { println("Closed db handle") }
fun shutDownFancyService(service: Service): IO<Unit> = IO { println("Closed service") }
//sampleStart
val managedTProgram = Resource.monad(IO.bracket()).fx.monad {
val consumer = Resource(::createConsumer, ::closeConsumer, IO.bracket()).bind()
val handle = Resource(::createDBHandle, ::closeDBHandle, IO.bracket()).bind()
Resource({ createFancyService(consumer, handle) }, ::shutDownFancyService, IO.bracket()).bind()
}.fix().use { service ->
// use service
// <...>
IO.unit
}.fix()
//sampleEnd
fun main() {
managedTProgram.unsafeRunSync()
}
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.
ap | fun <B> ap(BR: Bracket <F, E>, ff: ResourceOf <F, E, (A) -> B>): Resource <F, E, B> |
combine | fun combine(other: ResourceOf <F, E, A>, SR: Semigroup<A>, BR: Bracket <F, E>): Resource <F, E, A> |
flatMap | fun <B> flatMap(f: (A) -> ResourceOf <F, E, B>): Resource <F, E, B> |
invoke | operator fun <C> ~~invoke~~(use: (A) -> Kind<F, C>): Kind<F, C> |
map | fun <B> map(BR: Bracket <F, E>, f: (A) -> B): Resource <F, E, B> |
use | Use the created resource When done will run all finalizersfun <B> use(f: (A) -> Kind<F, B>): Kind<F, B> |
empty | Monoid empty. Creates an empty Resource using a given Monoid for A. Use with caution as the value will have no finalizers added.fun <F, E, A> empty(MR: Monoid<A>, BR: Bracket <F, E>): Resource <F, E, A> |
invoke | Construct a Resource from a allocating function acquire and a release function release.operator fun <F, E, A> invoke(acquire: () -> Kind<F, A>, release: (A, ExitCase <E>) -> Kind<F, Unit >, BR: Bracket <F, E>): Resource <F, E, A> operator fun <F, E, A> invoke(acquire: () -> Kind<F, A>, release: (A) -> Kind<F, Unit >, BR: Bracket <F, E>): Resource <F, E, A> |
just | Create a Resource from a value A. Use with caution as the value will have no finalizers added.fun <F, E, A> just(r: A, BR: Bracket <F, E>): Resource <F, E, A> |
liftF | Lift a value in context F into a Resource. Use with caution as the value will have no finalizers added.fun <F, E, A> Kind<F, A>.liftF(BR: Bracket <F, E>): Resource <F, E, A> |
tailRecM | fun <F, E, A, B> tailRecM(BR: Bracket <F, E>, a: A, f: (A) -> ResourceOf <F, E, Either<A, B>>): Resource <F, E, B> |
maybeCombine | fun <F, E, A> Resource <F, E, A>.~~maybeCombine~~(SR: Semigroup<A>, BR: Bracket <F, E>, arg1: Resource <F, E, A>): Resource <F, E, A> |
plus | fun <F, E, A> Resource <F, E, A>.~~plus~~(SR: Semigroup<A>, BR: Bracket <F, E>, arg1: Resource <F, E, A>): Resource <F, E, A> |
applicative | fun <F, E> Resource.Companion.~~applicative~~(BR: Bracket <F, E>): ResourceApplicative <F, E> |
functor | fun <F, E> Resource.Companion.~~functor~~(BR: Bracket <F, E>): ResourceFunctor <F, E> |
monad | fun <F, E> Resource.Companion.~~monad~~(BR: Bracket <F, E>): ResourceMonad <F, E> |
monadIO | fun <F, E> Resource.Companion.~~monadIO~~(FIO: MonadIO <F>, BR: Bracket <F, E>): ResourceMonadIO <F, E> |
monoid | fun <F, E, A> Resource.Companion.~~monoid~~(MR: Monoid<A>, BR: Bracket <F, E>): ResourceMonoid <F, E, A> |
selective | fun <F, E> Resource.Companion.~~selective~~(BR: Bracket <F, E>): ResourceSelective <F, E> |
semigroup | fun <F, E, A> Resource.Companion.~~semigroup~~(SR: Semigroup<A>, BR: Bracket <F, E>): ResourceSemigroup <F, E, A> |
Do you like Arrow?
✖