diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 06972248235..67a77efefad 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -25,6 +25,12 @@ jobs: distribution: 'zulu' java-version: 11 + # For watchOS x86 simulator. Remove with Kotlin 1.8. + - uses: maxim-lobanov/setup-xcode@v1 + if: matrix.os == 'macos-latest' + with: + xcode-version: '13.4.1' + - name: build uses: gradle/gradle-build-action@v2.2.0 if: matrix.os != 'windows-latest' diff --git a/arrow-libs/fx/arrow-fx-coroutines/api/arrow-fx-coroutines.api b/arrow-libs/fx/arrow-fx-coroutines/api/arrow-fx-coroutines.api index 29f3f19976f..d005a631ec6 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/api/arrow-fx-coroutines.api +++ b/arrow-libs/fx/arrow-fx-coroutines/api/arrow-fx-coroutines.api @@ -312,6 +312,7 @@ public final class arrow/fx/coroutines/Race3Kt { public abstract class arrow/fx/coroutines/Resource { public static final field Companion Larrow/fx/coroutines/Resource$Companion; + public final fun allocate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun allocated (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun ap (Larrow/fx/coroutines/Resource;)Larrow/fx/coroutines/Resource; public final fun flatMap (Lkotlin/jvm/functions/Function1;)Larrow/fx/coroutines/Resource; @@ -381,7 +382,10 @@ public final class arrow/fx/coroutines/ResourceKt { public static final fun release-zgiIeyo (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Larrow/fx/coroutines/Resource; public static final fun releaseCase (Larrow/fx/coroutines/Resource;Lkotlin/jvm/functions/Function3;)Larrow/fx/coroutines/Resource; public static final fun releaseCase-zgiIeyo (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Larrow/fx/coroutines/Resource; - public static final fun resource (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1; + public static final synthetic fun resource (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1; + public static final fun resource (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Larrow/fx/coroutines/Resource; + public static final fun resource (Lkotlin/jvm/functions/Function2;)Larrow/fx/coroutines/Resource; + public static final fun resourceScope (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun sequence (Ljava/lang/Iterable;)Larrow/fx/coroutines/Resource; public static final fun traverse (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Larrow/fx/coroutines/Resource; public static final fun traverseResource (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Larrow/fx/coroutines/Resource; @@ -517,11 +521,31 @@ public final class arrow/fx/coroutines/Use { public final synthetic fun unbox-impl ()Lkotlin/jvm/functions/Function1; } +public final class arrow/fx/coroutines/continuations/AcquireStep { + public static final field INSTANCE Larrow/fx/coroutines/continuations/AcquireStep; +} + +public abstract interface annotation class arrow/fx/coroutines/continuations/ResourceDSL : java/lang/annotation/Annotation { +} + public final class arrow/fx/coroutines/continuations/ResourceKt { public static final fun resource (Lkotlin/jvm/functions/Function2;)Larrow/fx/coroutines/Resource; } public abstract interface class arrow/fx/coroutines/continuations/ResourceScope { public abstract fun bind (Larrow/fx/coroutines/Resource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun install (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun onRelease (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun release (Larrow/fx/coroutines/Resource;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun releaseCase (Larrow/fx/coroutines/Resource;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class arrow/fx/coroutines/continuations/ResourceScope$DefaultImpls { + public static fun onRelease (Larrow/fx/coroutines/continuations/ResourceScope;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun release (Larrow/fx/coroutines/continuations/ResourceScope;Larrow/fx/coroutines/Resource;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun releaseCase (Larrow/fx/coroutines/continuations/ResourceScope;Larrow/fx/coroutines/Resource;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface annotation class arrow/fx/coroutines/continuations/ScopeDSL : java/lang/annotation/Annotation { } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/Resource.kt b/arrow-libs/fx/arrow-fx-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/Resource.kt index ce334ea2d50..4aba7585776 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/Resource.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/Resource.kt @@ -6,7 +6,8 @@ import arrow.core.identity import arrow.core.prependTo import arrow.core.zip import arrow.fx.coroutines.ExitCase.Companion.ExitCase -import arrow.fx.coroutines.continuations.ResourceScope +import arrow.fx.coroutines.continuations.AcquireStep +import arrow.fx.coroutines.continuations.ScopeDSL import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable @@ -16,29 +17,30 @@ import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext import kotlin.experimental.ExperimentalTypeInference +public typealias ResourceScope = arrow.fx.coroutines.continuations.ResourceScope + /** - * [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. + * [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. + * The capability of _installing_ resources is called [ResourceScope], and [Resource] defines the value associating the `acquisition` step, and the `finalizer`. + * [Resource] allocates and releases resources in a safe way that co-operates with Structured Concurrency, and KotlinX Coroutines. * - * 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. + * 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 compose other `suspend` functionality into resource safe programs, such as concurrency, parallelism or Arrow's Effect. * - * Consider the following use case: + * Creating a [Resource] _value_ can be done using the [resource] function, + * and running a program using [ResourceScope] can be done using [resourceScope], or [use]. + * Upon termination all finalizers are then guaranteed to run afterwards in reverse order of acquisition. * - * ```kotlin - * import arrow.fx.coroutines.* + * The following program is **not-safe** because it is prone to leak `dataSource` and `userProcessor` when an exception, or cancellation signal occurs whilst using the service. * + * ```kotlin * class UserProcessor { * fun start(): Unit = println("Creating UserProcessor") * fun shutdown(): Unit = println("Shutting down UserProcessor") - * fun process(ds: DataSource): List = - * ds.users().map { "Processed $it" } * } * * class DataSource { * fun connect(): Unit = println("Connecting dataSource") - * fun users(): List = listOf("User-1", "User-2", "User-3") * fun close(): Unit = println("Closed dataSource") * } * @@ -58,86 +60,210 @@ import kotlin.experimental.ExperimentalTypeInference * } * ``` * - * 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: + * If we were using Kotlin JVM, we might've relied on `Closeable` or `AutoCloseable` and rewritten our code to: * - * # Constructing Resource + * + * ```kotlin + * suspend fun main(): Unit { + * UserProcessor().use { userProcessor -> + * userProcessor.start() + * DataSource().use { dataSource -> + * dataSource.connect() + * Service(dataSource, userProcessor).processData() + * } + * } * } * ``` * * - * Here `releaseCase` also signals with what [ExitCase] state the `use` step finished. + * However, while we fixed closing of `UserProcessor` and `DataSource` there are issues still with this code: + * 1. It requires implementing `Closeable` or `AutoCloseable`, only possible for Kotlin JVM, not available for Kotlin MPP + * 2. Requires implementing interface, or wrapping external types with i.e. `class CloseableOf(val type: A): Closeable`. + * 3. Requires nesting of different resources in callback tree, not composable. + * 4. Enforces `close` method name, renamed `UserProcessor#shutdown` to `close` + * 5. Cannot run suspend functions upon _fun close(): Unit_. + * 6. No exit signal, we don't know if we exited successfully, with an error or cancellation. * - * # Using and composing Resource + * [Resource] solves of these issues. It defines 3 different steps: + * 1. Acquiring the resource of `A`. + * 2. Using `A`. + * 3. Releasing `A` with [ExitCase.Completed], [ExitCase.Failure] or [ExitCase.Cancelled]. * - * Arrow offers the same elegant `bind` DSL for Resource as you might be familiar with from Arrow Core. + * We rewrite our previous example to [Resource] below by: + * 1. Define [Resource] for `UserProcessor`. + * 2. Define [Resource] for `DataSource`, that also logs the [ExitCase]. + * 3. Compose `UserProcessor` and `DataSource` [Resource] together into a [Resource] for `Service`. * - * ```kotlin - * import arrow.fx.coroutines.* - * import arrow.fx.coroutines.continuations.resource + * + * ```kotlin + * val userProcessor: Resource = resource({ + * UserProcessor().also { it.start() } + * }) { p, _ -> p.shutdown() } * - * val userProcessor = resource { - * UserProcessor().also(UserProcessor::start) - * } release UserProcessor::shutdown - * - * val dataSource = resource { + * val dataSource: Resource = resource({ * DataSource().also { it.connect() } - * } release DataSource::close + * }) { ds, exitCase -> + * println("Releasing $ds with exit: $exitCase") + * withContext(Dispatchers.IO) { ds.close() } + * } * - * suspend fun main(): Unit { - * resource { - * parZip({ userProcessor.bind() }, { dataSource.bind() }) { userProcessor, ds -> - * Service(ds, userProcessor) - * } - * }.use { service -> service.processData() } + * val service: Resource = resource { + * Service(dataSource.bind(), userProcessor.bind()) + * } + * + * suspend fun main(): Unit = resourceScope { + * val data = service.bind().processData() + * println(data) * } * ``` * * - * [Resource]s are immutable and can be composed using [zip] or [parZip]. - * [Resource]s 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. + * There is a lot going on in the snippet above, which we'll analyse in the sections below. + * Looking at the above example it should already give you some idea if the capabilities of [Resource]. + * + * ## Resource constructors + * + * [Resource] works entirely through a DSL, + * which allows _installing_ a `Resource` through the `suspend fun install(acquire: suspend () -> A, release: suspend (A, ExitCase) -> Unit): A` function. + * + * `acquire` is used to _allocate_ the `Resource`, + * and before returning the resource `A` it also install the `release` handler into the `ResourceScope`. + * + * We can use `suspend fun` with `Scope` as an extension function receiver to create synthetic constructors for our `Resource`s. + * If you're using _context receivers_ you can also use `context(Scope)` instead. + * + * + * ```kotlin + * suspend fun ResourceScope.userProcessor(): UserProcessor = + * install({ UserProcessor().also { it.start() } }) { processor, _ -> + * processor.shutdown() + * } + * ``` + * + * We can of course also create `lazy` representations of this by wrapping `install` in [resource] and returning the `suspend lambda` value instead. + * + * ```kotlin + * val userProcessor: Resource = resource { + * val x: UserProcessor = install( + * { UserProcessor().also { it.start() } }, + * { processor, _ -> processor.shutdown() } + * ) + * x + * } + * ``` + * + * There is also a convenience operator for this pattern, but you might have preferred `ResourceScope::userProcessor` instead since it yields the same result. + * + * ```kotlin + * val userProcessor2: Resource = resource({ + * UserProcessor().also { it.start() } + * }) { processor, _ -> processor.shutdown() } + * ``` + * + * + * ## Scope DSL + * + * The [ResourceScope] DSL allows you to _install_ resources, and interact with them in a safe way. + * + * Arrow offers the same elegant `bind` DSL for Resource composition as you might be familiar with from Arrow Core. Which we've already seen above, in our first example. + * What is more interesting, is that we can also compose it with any other existing pattern from Arrow! + * Let's compose our `UserProcessor` and `DataSource` in parallel, so that their `start` and `connect` methods can run in parallel. + * + * + * ```kotlin + * suspend fun ResourceScope.userProcessor(): UserProcessor = + * install({ UserProcessor().also { it.start() } }){ p,_ -> p.shutdown() } + * + * suspend fun ResourceScope.dataSource(): DataSource = + * install({ DataSource().also { it.connect() } }) { ds, _ -> ds.close() } + * + * suspend fun main(): Unit = resourceScope { + * val service = parZip({ userProcessor() }, { dataSource() }) { userProcessor, ds -> + * Service(ds, userProcessor) + * } + * val data = service.processData() + * println(data) + * } + * ``` + * + * + * ## Conclusion + * + * [Resource] guarantee that their release finalizers are always invoked in the correct order when an exception is raised or the [kotlinx.coroutines.Job] 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. @@ -147,7 +273,7 @@ import kotlin.experimental.ExperimentalTypeInference * and automatic [NonCancellable] `acquire` and `release` steps use [bracketCase] or [bracket]. **/ public sealed class Resource { - + /** * Use the created resource * When done will run all finalizers @@ -171,7 +297,7 @@ public sealed class Resource { * .also(::println) * } * ``` - * + * */ @Suppress("UNCHECKED_CAST") public tailrec suspend infix fun use(f: suspend (A) -> B): B = @@ -192,30 +318,48 @@ public sealed class Resource { } b } - + is Allocate -> bracketCase(acquire, f, release) is Bind<*, *> -> Dsl { val any = source.bind() val ff = this@Resource.f as (Any?) -> Resource ff(any).bind() }.use(f) - + is Defer -> resource().use(f) } - + + @Deprecated( + "map $nextVersionRemoved", + ReplaceWith( + "resource { f(bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public fun map(f: suspend (A) -> B): Resource = - arrow.fx.coroutines.continuations.resource { f(bind()) } - + resource { f(bind()) } + /** Useful for setting up/configuring an acquired resource */ + @Deprecated( + "tap $nextVersionRemoved", + ReplaceWith( + "resource { bind().also { f(it) } }", + "arrow.fx.coroutines.resource" + ) + ) public fun tap(f: suspend (A) -> Unit): Resource = - arrow.fx.coroutines.continuations.resource { bind().also { f(it) } } - + resource { bind().also { f(it) } } + + @Deprecated( + "ap $nextVersionRemoved", + ReplaceWith( + "resource { bind().let { a -> ff.bind().invoke(a) } }", + "arrow.fx.coroutines.resource" + ) + ) public fun ap(ff: Resource<(A) -> B>): Resource = - arrow.fx.coroutines.continuations.resource { - val a = bind() - ff.bind()(a) - } - + resource { bind().let { a -> ff.bind().invoke(a) } } + /** * Create a resource value of [B] from a resource [A] by mapping [f]. * @@ -251,23 +395,42 @@ public sealed class Resource { * .use { println("Using database which uses dataSource") } * } * ``` - * + * * * @see zip to combine independent resources together * @see parZip for combining independent resources in parallel */ - public fun flatMap(f: (A) -> Resource): Resource = arrow.fx.coroutines.continuations.resource { - f(bind()).bind() - } - + + @Deprecated( + "flatMap $nextVersionRemoved", + ReplaceWith( + "resource { f(this.bind()).bind() }", + "arrow.fx.coroutines.resource" + ) + ) + public fun flatMap(f: (A) -> Resource): Resource = + resource { f(bind()).bind() } + + @Deprecated( + "zip $nextVersionRemoved", + ReplaceWith( + "resource { combine(this.bind(), other.bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public inline fun zip(other: Resource, crossinline combine: (A, B) -> C): Resource = - arrow.fx.coroutines.continuations.resource { - combine(bind(), other.bind()) - } - + resource { combine(bind(), other.bind()) } + + @Deprecated( + "zip $nextVersionRemoved", + ReplaceWith( + "resource { Pair(this.bind(), other.bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public fun zip(other: Resource): Resource> = zip(other, ::Pair) - + /** * Combines two independent resource values with the provided [map] function, * returning the resulting immutable [Resource] value. @@ -311,53 +474,80 @@ public sealed class Resource { * }.use { service -> service.processData() } * } * ``` - * + * * * @see parZip if you want to combine independent resources in parallel * @see flatMap to combine resources that rely on each-other. */ + @Deprecated( + "zip $nextVersionRemoved", + ReplaceWith( + "resource { map(this.bind(), b.bind(), c.bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public inline fun zip( b: Resource, c: Resource, - crossinline map: (A, B, C) -> D + crossinline map: (A, B, C) -> D, ): Resource = - arrow.fx.coroutines.continuations.resource { - map(bind(), b.bind(), c.bind()) - } - + resource { map(bind(), b.bind(), c.bind()) } + + @Deprecated( + "zip $nextVersionRemoved", + ReplaceWith( + "resource { map(this.bind(), b.bind(), c.bind(), d.bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public inline fun zip( b: Resource, c: Resource, d: Resource, - crossinline map: (A, B, C, D) -> E + crossinline map: (A, B, C, D) -> E, ): Resource = - arrow.fx.coroutines.continuations.resource { - map(bind(), b.bind(), c.bind(), d.bind()) - } - + resource { map(bind(), b.bind(), c.bind(), d.bind()) } + + @Deprecated( + "zip $nextVersionRemoved", + ReplaceWith( + "resource { map(this.bind(), b.bind(), c.bind(), d.bind(), e.bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public inline fun zip( b: Resource, c: Resource, d: Resource, e: Resource, - crossinline map: (A, B, C, D, E) -> G + crossinline map: (A, B, C, D, E) -> G, ): Resource = - arrow.fx.coroutines.continuations.resource { - map(bind(), b.bind(), c.bind(), d.bind(), e.bind()) - } - + resource { map(bind(), b.bind(), c.bind(), d.bind(), e.bind()) } + + @Deprecated( + "This method will be removed in Arrow 2.x.x in favor of the DSL", + ReplaceWith( + "zip $nextVersionRemoved", + "arrow.fx.coroutines.resource" + ) + ) public inline fun zip( b: Resource, c: Resource, d: Resource, e: Resource, f: Resource, - crossinline map: (A, B, C, D, E, F) -> G + crossinline map: (A, B, C, D, E, F) -> G, ): Resource = - arrow.fx.coroutines.continuations.resource { - map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind()) - } - + resource { map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind()) } + + @Deprecated( + "zip $nextVersionRemoved", + ReplaceWith( + "resource { map(this.bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public inline fun zip( b: Resource, c: Resource, @@ -365,12 +555,17 @@ public sealed class Resource { e: Resource, f: Resource, g: Resource, - crossinline map: (A, B, C, D, E, F, G) -> H + crossinline map: (A, B, C, D, E, F, G) -> H, ): Resource = - arrow.fx.coroutines.continuations.resource { - map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind()) - } - + resource { map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind()) } + + @Deprecated( + "zip $nextVersionRemoved", + ReplaceWith( + "resource { map(this.bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public inline fun zip( b: Resource, c: Resource, @@ -379,12 +574,17 @@ public sealed class Resource { f: Resource, g: Resource, h: Resource, - crossinline map: (A, B, C, D, E, F, G, H) -> I + crossinline map: (A, B, C, D, E, F, G, H) -> I, ): Resource = - arrow.fx.coroutines.continuations.resource { - map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind()) - } - + resource { map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind()) } + + @Deprecated( + "zip $nextVersionRemoved", + ReplaceWith( + "resource { map(this.bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind(), i.bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public inline fun zip( b: Resource, c: Resource, @@ -394,12 +594,17 @@ public sealed class Resource { g: Resource, h: Resource, i: Resource, - crossinline map: (A, B, C, D, E, F, G, H, I) -> J + crossinline map: (A, B, C, D, E, F, G, H, I) -> J, ): Resource = - arrow.fx.coroutines.continuations.resource { - map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind(), i.bind()) - } - + resource { map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind(), i.bind()) } + + @Deprecated( + "zip $nextVersionRemoved", + ReplaceWith( + "resource { map(this.bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind(), i.bind(), j.bind()) }", + "arrow.fx.coroutines.resource" + ) + ) public inline fun zip( b: Resource, c: Resource, @@ -410,15 +615,20 @@ public sealed class Resource { h: Resource, i: Resource, j: Resource, - crossinline map: (A, B, C, D, E, F, G, H, I, J) -> K + crossinline map: (A, B, C, D, E, F, G, H, I, J) -> K, ): Resource = - arrow.fx.coroutines.continuations.resource { - map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind(), i.bind(), j.bind()) - } - + resource { map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind(), i.bind(), j.bind()) } + + @Deprecated( + "parZip $nextVersionRemoved", + ReplaceWith( + "resource { parZip({ this.bind() }, { fb.bind() }) { a, b -> f(a, b) } }", + "arrow.fx.coroutines.resource" + ) + ) public fun parZip(fb: Resource, f: suspend (A, B) -> C): Resource = - parZip(Dispatchers.Default, fb, f) - + resource { parZip({ bind() }, { fb.bind() }) { a, b -> f(a, b) } } + /** * Composes two [Resource]s together by zipping them in parallel, * by running both their `acquire` handlers in parallel, and both `release` handlers in parallel. @@ -460,110 +670,162 @@ public sealed class Resource { * }.use { service -> service.processData() } * } * ``` - * + * */ + @Deprecated( + "parZip $nextVersionRemoved", + ReplaceWith( + "resource { parZip(ctx, { this.bind() }, { fb.bind() }) { a, b -> f(a, b) } }", + "arrow.fx.coroutines.resource" + ) + ) public fun parZip( ctx: CoroutineContext = Dispatchers.Default, fb: Resource, - f: suspend (A, B) -> C + f: suspend (A, B) -> C, ): Resource = - arrow.fx.coroutines.continuations.resource { + resource { parZip(ctx, { this@Resource.bind() }, { fb.bind() }) { a, b -> f(a, b) } } - + + @Deprecated("Use the safer version allocate instead.") + @DelicateCoroutinesApi + public suspend fun allocated(): Pair A, suspend (@UnsafeVariance A, ExitCase) -> Unit> = + when (this) { + is Bind<*, A> -> + Dsl { + val any = source.bind() + val ff = f as (Any?) -> Resource + ff(any).bind() + }.allocated() + + is Allocate -> acquire to release + is Defer -> resource().allocated() + is Dsl -> { + val effect = ResourceScopeImpl() + val allocated = try { + val allocate: suspend () -> A = suspend { dsl(effect) } + val release: suspend (A, ExitCase) -> Unit = { _, e -> + effect.finalizers.get().cancelAll(e)?.let { throw it } + } + allocate to release + } catch (e: Throwable) { + val ee = withContext(NonCancellable) { + effect.finalizers.get().cancelAll(ExitCase(e), e) ?: e + } + throw ee + } + allocated + } + } + /** - * 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`. + * Deconstruct [Resource] into an [A] and a `release` handler. + * The `release` action **must** always be called, if never called, then the resource [A] will leak. + * The `release` step is already made `NonCancellable` to guarantee correct invocation like `Resource` or `bracketCase`, + * and it will automatically rethrow, and compose, the exceptions as needed. * * ```kotlin * import arrow.fx.coroutines.* * import arrow.fx.coroutines.ExitCase.Companion.ExitCase * - * val resource = Resource({ println("Acquire") }) { _, exitCase -> - * println("Release $exitCase") - * } + * val resource = + * resource({ "Acquire" }) { _, exitCase -> println("Release $exitCase") } * * suspend fun main(): Unit { - * val (acquire, release) = resource.allocated() - * val a = acquire() + * val (acquired: String, release: suspend (ExitCase) -> Unit) = resource.allocate() * try { * /** Do something with A */ - * release(a, ExitCase.Completed) + * release(ExitCase.Completed) * } catch(e: Throwable) { - * val e2 = runCatching { release(a, ExitCase(e)) }.exceptionOrNull() - * throw Platform.composeErrors(e, e2) + * release(ExitCase(e)) * } * } * ``` - * + * * * This is a **delicate** API. It is easy to accidentally create resource or memory leaks `allocated` is used. - * A `Resource` allocated by `allocated` is not subject to the guarantees that [Resource] makes, + * A `Resource` allocated by `allocate` is not subject to the guarantees that [Resource] makes, * instead the caller is responsible for correctly invoking the `release` handler at the appropriate time. * This API is useful for building inter-op APIs between [Resource] and non-suspending code, such as Java libraries. */ @DelicateCoroutinesApi - public suspend fun allocated(): Pair A, suspend (@UnsafeVariance A, ExitCase) -> Unit> = + public suspend fun allocate(): Pair Unit> = when (this) { is Bind<*, A> -> Dsl { val any = source.bind() val ff = f as (Any?) -> Resource ff(any).bind() - }.allocated() - is Allocate -> acquire to release - is Defer -> resource().allocated() + }.allocate() + + is Allocate -> { + val a = acquire() + Pair(a) { exitCase -> release(a, exitCase) } + } + + is Defer -> resource().allocate() is Dsl -> { val effect = ResourceScopeImpl() - val allocated = try { - val allocate: suspend () -> A = suspend { dsl(effect) } - val release: suspend (A, ExitCase) -> Unit = { _, e -> - effect.finalizers.get().cancelAll(e)?.let { throw it } - } - allocate to release - } catch (e: Throwable) { - val ee = withContext(NonCancellable) { - effect.finalizers.get().cancelAll(ExitCase(e), e) ?: e + val allocated: A = dsl(effect) + val release: suspend (ExitCase) -> Unit = { e -> + val suppressed: Throwable? = effect.finalizers.get().cancelAll(e) + val original: Throwable? = when (e) { + ExitCase.Completed -> null + is ExitCase.Cancelled -> e.exception + is ExitCase.Failure -> e.failure } - throw ee + Platform.composeErrors(original, suppressed)?.let { throw it } } - allocated + Pair(allocated, release) } } - + @Deprecated( - "Bind is being deprecated. Use resource DSL instead", + "Bind $nextVersionRemoved", ReplaceWith( "resource { f(source.bind()) }", - "arrow.fx.coroutines.continuations.resource" + "arrow.fx.coroutines.resource" ) ) public class Bind(public val source: Resource, public val f: (A) -> Resource) : Resource() - + + @Deprecated( + "Allocate $nextVersionRemoved", + ReplaceWith( + "resource(acquire, release)", + "arrow.fx.coroutines.resource" + ) + ) public class Allocate( public val acquire: suspend () -> A, - public val release: suspend (A, ExitCase) -> Unit + public val release: suspend (A, ExitCase) -> Unit, ) : Resource() - + @Deprecated( - "Defer is being deprecated. Use resource DSL instead", + "Defer $nextVersionRemoved", ReplaceWith( "resource { resource.invoke().bind() }", - "arrow.fx.coroutines.continuations.resource" + "arrow.fx.coroutines.resource" ) ) public class Defer(public val resource: suspend () -> Resource) : Resource() - + + @Deprecated( + "Dsl $nextVersionRemoved", + ReplaceWith( + "resource { dsl() }", + "arrow.fx.coroutines.resource" + ) + ) public data class Dsl(public val dsl: suspend ResourceScope.() -> A) : Resource() - + public companion object { - + @PublishedApi @Deprecated("This will be removed from the binary in Arrow 2.0", level = DeprecationLevel.ERROR) internal val unit: Resource = just(Unit) - + /** * Construct a [Resource] from an allocating function [acquire] and a release function [release]. * @@ -580,24 +842,38 @@ public sealed class Resource { * } * } * ``` - * + * */ + @Deprecated( + "Operator invoke is replaced with top-level function", + ReplaceWith( + "resource(acquire, release)", + "arrow.fx.coroutines.resource" + ) + ) public operator fun invoke( acquire: suspend () -> A, - release: suspend (A, ExitCase) -> Unit + release: suspend (A, ExitCase) -> Unit, ): Resource = Allocate(acquire, release) - + /** * Create a [Resource] from a pure value [A]. */ + @Deprecated( + "Use the resource DSL to create Resource values. Will be removed in Arrow 2.x.x", + ReplaceWith( + "resource { r }", + "arrow.fx.coroutines.resource" + ) + ) public fun just(r: A): Resource = Resource({ r }, { _, _ -> Unit }) - + @Deprecated( "defer is being deprecated. Use resource DSL instead", ReplaceWith( "resource { f().bind() }", - "arrow.fx.coroutines.continuations.resource" + "arrow.fx.coroutines.resource" ) ) public fun defer(f: suspend () -> Resource): Resource = @@ -605,6 +881,17 @@ public sealed class Resource { } } +public fun resource( + acquire: suspend () -> A, + release: suspend (A, ExitCase) -> Unit, +): Resource = resource { + install({ acquire() }, release) +} + +@ScopeDSL +public suspend fun resourceScope(action: suspend ResourceScope.() -> A): A = + resource(action).use(::identity) + /** * Marker for `suspend () -> A` to be marked as the [Use] action of a [Resource]. * Offers a convenient DSL to use [Resource] for simple resources. @@ -634,13 +921,13 @@ public sealed class Resource { * println(res) * } * ``` - * + * */ @Deprecated( "Use the resource computation DSL instead", ReplaceWith( "resource { acquire() }", - "arrow.fx.coroutines.continuations.resource" + "arrow.fx.coroutines.resource" ) ) public inline class Use(internal val acquire: suspend () -> A) @@ -652,11 +939,16 @@ public inline class Use(internal val acquire: suspend () -> A) "Use the resource computation DSL instead", ReplaceWith( "resource { acquire() }", - "arrow.fx.coroutines.continuations.resource" - ) + "arrow.fx.coroutines.resource" + ), + level = DeprecationLevel.HIDDEN ) public fun resource(acquire: suspend () -> A): Use = Use(acquire) +@ScopeDSL +public fun resource(action: suspend ResourceScope.() -> A): Resource = + arrow.fx.coroutines.continuations.resource(action) + @Deprecated("Use the resource computation DSL instead") public infix fun Use.release(release: suspend (A) -> Unit): Resource = Resource(acquire) { a, _ -> release(a) } @@ -665,7 +957,7 @@ public infix fun Use.release(release: suspend (A) -> Unit): Resource = * Composes a [release] action to a [Resource.use] action creating a [Resource]. */ public infix fun Resource.release(release: suspend (A) -> Unit): Resource = - arrow.fx.coroutines.continuations.resource { + resource { val a = bind() Resource({ a }, { _, _ -> release(a) }).bind() } @@ -678,14 +970,14 @@ public infix fun Use.releaseCase(release: suspend (A, ExitCase) -> Unit): * Composes a [releaseCase] action to a [Resource.use] action creating a [Resource]. */ public infix fun Resource.releaseCase(release: suspend (A, ExitCase) -> Unit): Resource = - arrow.fx.coroutines.continuations.resource { + resource { val a = bind() Resource({ a }, { _, ex -> release(a, ex) }).bind() } @Deprecated("traverseResource is being renamed to traverse to simplify the Arrow API", ReplaceWith("traverse(f)")) public inline fun Iterable.traverseResource(crossinline f: (A) -> Resource): Resource> = - arrow.fx.coroutines.continuations.resource { + resource { map { a -> f(a).bind() } @@ -724,16 +1016,19 @@ public inline fun Iterable.traverseResource(crossinline f: (A) -> Reso * res.forEach(::println) * } * ``` - * + * */ +@Deprecated( + "Use the resource computation DSL instead", + ReplaceWith( + "resource { map { a -> f(a).bind() } }", + "arrow.fx.coroutines.resource" + ) +) @OptIn(ExperimentalTypeInference::class) @OverloadResolutionByLambdaReturnType public inline fun Iterable.traverse(crossinline f: (A) -> Resource): Resource> = - arrow.fx.coroutines.continuations.resource { - map { a -> - f(a).bind() - } - } + resource { map { a -> f(a).bind() } } /** * Sequences this [Iterable] of [Resource]s. @@ -769,11 +1064,18 @@ public inline fun Iterable.traverse(crossinline f: (A) -> Resource) * res.forEach(::println) * } * ``` - * + * */ +@Deprecated( + "Use the resource computation DSL instead", + ReplaceWith( + "resource { map { a -> a.bind() } }", + "arrow.fx.coroutines.resource" + ) +) @Suppress("NOTHING_TO_INLINE") public inline fun Iterable>.sequence(): Resource> = - traverse(::identity) + resource { map { a -> a.bind() } } /** * Runs [Resource.use] and emits [A] of the resource @@ -822,7 +1124,7 @@ private class ResourceScopeImpl : ResourceScope { (e?.apply { e2?.let(::addSuppressed) } ?: e2)?.let { throw it } } }) - + is Resource.Bind<*, *> -> { val dsl: suspend ResourceScope.() -> A = { val any = source.bind() @@ -831,17 +1133,37 @@ private class ResourceScopeImpl : ResourceScope { } dsl(this@ResourceScopeImpl) } - + is Resource.Defer -> resource().bind() } + + override suspend fun install(acquire: suspend AcquireStep.() -> A, release: suspend (A, ExitCase) -> Unit): A = + bracketCase({ + val a = acquire(AcquireStep) + val finalizer: suspend (ExitCase) -> Unit = { ex: ExitCase -> release(a, ex) } + finalizers.update(finalizer::prependTo) + a + }, ::identity, { a, ex -> + // Only if ExitCase.Failure, or ExitCase.Cancelled during acquire we cancel + // Otherwise we've saved the finalizer, and it will be called from somewhere else. + if (ex != ExitCase.Completed) { + val e = finalizers.get().cancelAll(ex) + val e2 = kotlin.runCatching { release(a, ex) }.exceptionOrNull() + Platform.composeErrors(e, e2)?.let { throw it } + } + }) } private suspend fun List Unit>.cancelAll( exitCase: ExitCase, - first: Throwable? = null + first: Throwable? = null, ): Throwable? = fold(first) { acc, finalizer -> val other = kotlin.runCatching { finalizer(exitCase) }.exceptionOrNull() other?.let { acc?.apply { addSuppressed(other) } ?: other } ?: acc } + +private const val nextVersionRemoved: String = + "is redundant and will be removed in Arrow 2.x.x in favor of the DSL.\n" + + "In case you think this method should stay, please provide feedback and your use-case on https://github.com/arrow-kt/arrow/issues" diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/continuations/resource.kt b/arrow-libs/fx/arrow-fx-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/continuations/resource.kt index 5b9e414771d..8c5e53a31cd 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/continuations/resource.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/continuations/resource.kt @@ -1,6 +1,22 @@ package arrow.fx.coroutines.continuations +import arrow.fx.coroutines.ExitCase +import arrow.fx.coroutines.ExitCase.Companion.ExitCase import arrow.fx.coroutines.Resource +import arrow.fx.coroutines.releaseCase + +@DslMarker +public annotation class ScopeDSL + +@DslMarker +public annotation class ResourceDSL + +/** + * This Marker exists to prevent being able to call `bind` from `install`, and its derived methods. + * This is done to ensure correct usage of [ResourceScope]. + */ +@ResourceDSL +public object AcquireStep /** * Computation block for the [Resource] type. @@ -49,9 +65,41 @@ import arrow.fx.coroutines.Resource * ``` * */ +@ResourceDSL public interface ResourceScope { + + @ResourceDSL public suspend fun Resource.bind(): A + + /** + * Install [A] into the [ResourceScope]. + * It's [release] function will be called with the appropriate [ExitCase] if this [ResourceScope] finishes. + * It results either in [ExitCase.Completed], [ExitCase.Cancelled] or [ExitCase.Failure] depending on the terminal state of [Resource] lambda. + */ + @ResourceDSL + public suspend fun install( + acquire: suspend AcquireStep.() -> A, + release: suspend (A, ExitCase) -> Unit, + ): A + + /** Composes a [release] action to a [Resource] value before binding. */ + @ResourceDSL + public suspend infix fun Resource.release(release: suspend (A) -> Unit): A { + val a = bind() + return install({ a }) { a, _ -> release(a) } + } + + /** Composes a [releaseCase] action to a [Resource] value before binding. */ + @ResourceDSL + public suspend infix fun Resource.releaseCase(release: suspend (A, ExitCase) -> Unit): A { + val a = bind() + return install({ a }, release) + } + + public suspend infix fun onRelease(release: suspend (ExitCase) -> Unit): Unit = + install({ }) { _, exitCase -> release(exitCase) } } +@ScopeDSL public fun resource(f: suspend ResourceScope.() -> A): Resource = Resource.Dsl(f) diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-01.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-01.kt index 12cb1a9be07..93ee7786688 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-01.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-01.kt @@ -1,18 +1,13 @@ // This file was automatically generated from Resource.kt by Knit tool. Do not edit. package arrow.fx.coroutines.examples.exampleResource01 -import arrow.fx.coroutines.* - class UserProcessor { fun start(): Unit = println("Creating UserProcessor") fun shutdown(): Unit = println("Shutting down UserProcessor") - fun process(ds: DataSource): List = - ds.users().map { "Processed $it" } } class DataSource { fun connect(): Unit = println("Connecting dataSource") - fun users(): List = listOf("User-1", "User-2", "User-3") fun close(): Unit = println("Closed dataSource") } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-02.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-02.kt index 73bc6a73dc9..9d0a4061988 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-02.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-02.kt @@ -1,16 +1,28 @@ // This file was automatically generated from Resource.kt by Knit tool. Do not edit. package arrow.fx.coroutines.examples.exampleResource02 -import arrow.fx.coroutines.* +import java.io.Closeable -val resourceA = resource { - "A" -} release { a -> - println("Releasing $a") +class UserProcessor : Closeable { + fun start(): Unit = println("Creating UserProcessor") + override fun close(): Unit = println("Shutting down UserProcessor") } -val resourceB = resource { - "B" -} releaseCase { b, exitCase -> - println("Releasing $b with exit: $exitCase") +class DataSource : Closeable { + fun connect(): Unit = println("Connecting dataSource") + override fun close(): Unit = println("Closed dataSource") +} + +class Service(val db: DataSource, val userProcessor: UserProcessor) { + suspend fun processData(): List = throw RuntimeException("I'm going to leak resources by not closing them") +} + +suspend fun main(): Unit { + UserProcessor().use { userProcessor -> + userProcessor.start() + DataSource().use { dataSource -> + dataSource.connect() + Service(dataSource, userProcessor).processData() + } + } } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-03.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-03.kt index d369bbbf7fd..2d977c79930 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-03.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-03.kt @@ -1,38 +1,44 @@ // This file was automatically generated from Resource.kt by Knit tool. Do not edit. package arrow.fx.coroutines.examples.exampleResource03 -import arrow.fx.coroutines.* -import arrow.fx.coroutines.continuations.resource +import arrow.fx.coroutines.Resource +import arrow.fx.coroutines.resource +import arrow.fx.coroutines.resourceScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class UserProcessor { - fun start(): Unit = println("Creating UserProcessor") - fun shutdown(): Unit = println("Shutting down UserProcessor") - fun process(ds: DataSource): List = - ds.users().map { "Processed $it" } + suspend fun start(): Unit = withContext(Dispatchers.IO) { println("Creating UserProcessor") } + suspend fun shutdown(): Unit = withContext(Dispatchers.IO) { + println("Shutting down UserProcessor") + } } class DataSource { fun connect(): Unit = println("Connecting dataSource") - fun users(): List = 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 = userProcessor.process(db) + suspend fun processData(): List = throw RuntimeException("I'm going to leak resources by not closing them") } -val userProcessor = resource { - UserProcessor().also(UserProcessor::start) -} release UserProcessor::shutdown +val userProcessor: Resource = resource({ + UserProcessor().also { it.start() } +}) { p, _ -> p.shutdown() } -val dataSource = resource { +val dataSource: Resource = 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() } +}) { ds, exitCase -> + println("Releasing $ds with exit: $exitCase") + withContext(Dispatchers.IO) { ds.close() } +} + +val service: Resource = resource { + Service(dataSource.bind(), userProcessor.bind()) +} + +suspend fun main(): Unit = resourceScope { + val data = service.bind().processData() + println(data) } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-04.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-04.kt index f3f67d497cc..8e192f4705d 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-04.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-04.kt @@ -1,20 +1,32 @@ // This file was automatically generated from Resource.kt by Knit tool. Do not edit. package arrow.fx.coroutines.examples.exampleResource04 -import arrow.fx.coroutines.* +import arrow.fx.coroutines.ResourceScope +import arrow.fx.coroutines.Resource +import arrow.fx.coroutines.resource +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext -class DataSource { - fun connect(): Unit = println("Connecting dataSource") - fun users(): List = listOf("User-1", "User-2", "User-3") - fun close(): Unit = println("Closed dataSource") +class UserProcessor { + suspend fun start(): Unit = withContext(Dispatchers.IO) { println("Creating UserProcessor") } + suspend fun shutdown(): Unit = withContext(Dispatchers.IO) { + println("Shutting down UserProcessor") + } } -suspend fun main(): Unit { - val dataSource = resource { - DataSource().also { it.connect() } - } release DataSource::close +suspend fun ResourceScope.userProcessor(): UserProcessor = + install({ UserProcessor().also { it.start() } }) { processor, _ -> + processor.shutdown() + } - val res = dataSource - .use { ds -> "Using data source: ${ds.users()}" } - .also(::println) +val userProcessor: Resource = resource { + val x: UserProcessor = install( + { UserProcessor().also { it.start() } }, + { processor, _ -> processor.shutdown() } + ) + x } + +val userProcessor2: Resource = resource({ + UserProcessor().also { it.start() } +}) { processor, _ -> processor.shutdown() } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-05.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-05.kt index 24d8787a74b..ce20f638266 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-05.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-05.kt @@ -1,30 +1,38 @@ // This file was automatically generated from Resource.kt by Knit tool. Do not edit. package arrow.fx.coroutines.examples.exampleResource05 -import arrow.fx.coroutines.* +import arrow.fx.coroutines.ResourceScope +import arrow.fx.coroutines.resourceScope +import arrow.fx.coroutines.parZip +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class UserProcessor { + suspend fun start(): Unit = withContext(Dispatchers.IO) { println("Creating UserProcessor") } + suspend fun shutdown(): Unit = withContext(Dispatchers.IO) { + println("Shutting down UserProcessor") + } +} -object Connection class DataSource { - fun connect(): Unit = println("Connecting dataSource") - fun connection(): Connection = Connection - fun close(): Unit = println("Closed dataSource") + suspend fun connect(): Unit = withContext(Dispatchers.IO) { println("Connecting dataSource") } + suspend fun close(): Unit = withContext(Dispatchers.IO) { println("Closed dataSource") } } -class Database(private val database: DataSource) { - fun init(): Unit = println("Database initialising . . .") - fun shutdown(): Unit = println("Database shutting down . . .") +class Service(val db: DataSource, val userProcessor: UserProcessor) { + suspend fun processData(): List = (0..10).map { "Processed : $it" } } -suspend fun main(): Unit { - val dataSource = resource { - DataSource().also { it.connect() } - } release DataSource::close +suspend fun ResourceScope.userProcessor(): UserProcessor = + install({ UserProcessor().also { it.start() } }){ p,_ -> p.shutdown() } - fun database(ds: DataSource): Resource = - resource { - Database(ds).also(Database::init) - } release Database::shutdown +suspend fun ResourceScope.dataSource(): DataSource = + install({ DataSource().also { it.connect() } }) { ds, _ -> ds.close() } - dataSource.flatMap(::database) - .use { println("Using database which uses dataSource") } +suspend fun main(): Unit = resourceScope { + val service = parZip({ userProcessor() }, { dataSource() }) { userProcessor, ds -> + Service(ds, userProcessor) + } + val data = service.processData() + println(data) } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-06.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-06.kt index 327b70776c7..68cae307ffe 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-06.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-06.kt @@ -3,33 +3,18 @@ package arrow.fx.coroutines.examples.exampleResource06 import arrow.fx.coroutines.* -class UserProcessor { - fun start(): Unit = println("Creating UserProcessor") - fun shutdown(): Unit = println("Shutting down UserProcessor") - fun process(ds: DataSource): List = - ds.users().map { "Processed $it" } -} - class DataSource { fun connect(): Unit = println("Connecting dataSource") fun users(): List = 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 = 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 { - userProcessor.zip(dataSource) { userProcessor, ds -> - Service(ds, userProcessor) - }.use { service -> service.processData() } + val dataSource = resource { + DataSource().also { it.connect() } + } release DataSource::close + + val res = dataSource + .use { ds -> "Using data source: ${ds.users()}" } + .also(::println) } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-07.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-07.kt index 2a2f16c1d4f..e272f9a54f5 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-07.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-07.kt @@ -2,35 +2,29 @@ package arrow.fx.coroutines.examples.exampleResource07 import arrow.fx.coroutines.* -import kotlinx.coroutines.delay - -class UserProcessor { - suspend fun start(): Unit { delay(750); println("Creating UserProcessor") } - fun shutdown(): Unit = println("Shutting down UserProcessor") - fun process(ds: DataSource): List = - ds.users().map { "Processed $it" } -} +object Connection class DataSource { - suspend fun connect(): Unit { delay(1000); println("Connecting dataSource") } - fun users(): List = listOf("User-1", "User-2", "User-3") + fun connect(): Unit = println("Connecting dataSource") + fun connection(): Connection = Connection fun close(): Unit = println("Closed dataSource") } -class Service(val db: DataSource, val userProcessor: UserProcessor) { - suspend fun processData(): List = userProcessor.process(db) +class Database(private val database: DataSource) { + fun init(): Unit = println("Database initialising . . .") + fun shutdown(): Unit = println("Database shutting down . . .") } -val userProcessor = resource { - UserProcessor().also { it.start() } -} release UserProcessor::shutdown +suspend fun main(): Unit { + val dataSource = resource { + DataSource().also { it.connect() } + } release DataSource::close -val dataSource = resource { - DataSource().also { it.connect() } -} release DataSource::close + fun database(ds: DataSource): Resource = + resource { + Database(ds).also(Database::init) + } release Database::shutdown -suspend fun main(): Unit { - userProcessor.parZip(dataSource) { userProcessor, ds -> - Service(ds, userProcessor) - }.use { service -> service.processData() } + dataSource.flatMap(::database) + .use { println("Using database which uses dataSource") } } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-08.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-08.kt index fa5813146e6..fb14ed2cc30 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-08.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-08.kt @@ -2,20 +2,34 @@ package arrow.fx.coroutines.examples.exampleResource08 import arrow.fx.coroutines.* -import arrow.fx.coroutines.ExitCase.Companion.ExitCase -val resource = Resource({ println("Acquire") }) { _, exitCase -> - println("Release $exitCase") +class UserProcessor { + fun start(): Unit = println("Creating UserProcessor") + fun shutdown(): Unit = println("Shutting down UserProcessor") + fun process(ds: DataSource): List = + ds.users().map { "Processed $it" } } +class DataSource { + fun connect(): Unit = println("Connecting dataSource") + fun users(): List = 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 = 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 { - val (acquire, release) = resource.allocated() - val a = acquire() - try { - /** Do something with A */ - release(a, ExitCase.Completed) - } catch(e: Throwable) { - val e2 = runCatching { release(a, ExitCase(e)) }.exceptionOrNull() - throw Platform.composeErrors(e, e2) - } + userProcessor.zip(dataSource) { userProcessor, ds -> + Service(ds, userProcessor) + }.use { service -> service.processData() } } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-09.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-09.kt index d492b4e97b7..6921b8e93cf 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-09.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-09.kt @@ -2,13 +2,35 @@ package arrow.fx.coroutines.examples.exampleResource09 import arrow.fx.coroutines.* +import kotlinx.coroutines.delay -suspend fun acquireResource(): Int = 42.also { println("Getting expensive resource") } -suspend fun releaseResource(r: Int, exitCase: ExitCase): Unit = println("Releasing expensive resource: $r, exit: $exitCase") +class UserProcessor { + suspend fun start(): Unit { delay(750); println("Creating UserProcessor") } + fun shutdown(): Unit = println("Shutting down UserProcessor") + fun process(ds: DataSource): List = + ds.users().map { "Processed $it" } +} + +class DataSource { + suspend fun connect(): Unit { delay(1000); println("Connecting dataSource") } + fun users(): List = 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 = userProcessor.process(db) +} + +val userProcessor = resource { + UserProcessor().also { it.start() } +} release UserProcessor::shutdown + +val dataSource = resource { + DataSource().also { it.connect() } +} release DataSource::close suspend fun main(): Unit { - val resource = Resource(::acquireResource, ::releaseResource) - resource.use { - println("Expensive resource under use! $it") - } + userProcessor.parZip(dataSource) { userProcessor, ds -> + Service(ds, userProcessor) + }.use { service -> service.processData() } } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-10.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-10.kt index d0bd9907f85..ffba3327d3d 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-10.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-10.kt @@ -2,25 +2,17 @@ package arrow.fx.coroutines.examples.exampleResource10 import arrow.fx.coroutines.* +import arrow.fx.coroutines.ExitCase.Companion.ExitCase -class File(url: String) { - suspend fun open(): File = this - suspend fun close(): Unit {} - override fun toString(): String = "This file contains some interesting content!" -} - -suspend fun openFile(uri: String): File = File(uri).open() -suspend fun closeFile(file: File): Unit = file.close() -suspend fun fileToString(file: File): String = file.toString() +val resource = + resource({ "Acquire" }) { _, exitCase -> println("Release $exitCase") } suspend fun main(): Unit { - val res = resource { - openFile("data.json") - } release { file -> - closeFile(file) - } use { file -> - fileToString(file) + val (acquired: String, release: suspend (ExitCase) -> Unit) = resource.allocate() + try { + /** Do something with A */ + release(ExitCase.Completed) + } catch(e: Throwable) { + release(ExitCase(e)) } - - println(res) } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-11.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-11.kt index 7a2ed86df12..b4370b2eedb 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-11.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-11.kt @@ -3,29 +3,12 @@ package arrow.fx.coroutines.examples.exampleResource11 import arrow.fx.coroutines.* -class File(url: String) { - suspend fun open(): File = this - suspend fun close(): Unit {} - override fun toString(): String = "This file contains some interesting content!" -} - -suspend fun openFile(uri: String): File = File(uri).open() -suspend fun closeFile(file: File): Unit = file.close() -suspend fun fileToString(file: File): String = file.toString() +suspend fun acquireResource(): Int = 42.also { println("Getting expensive resource") } +suspend fun releaseResource(r: Int, exitCase: ExitCase): Unit = println("Releasing expensive resource: $r, exit: $exitCase") suspend fun main(): Unit { - val res: List = listOf( - "data.json", - "user.json", - "resource.json" - ).traverse { uri -> - resource { - openFile(uri) - } release { file -> - closeFile(file) - } - }.use { files -> - files.map { fileToString(it) } + val resource = Resource(::acquireResource, ::releaseResource) + resource.use { + println("Expensive resource under use! $it") } - res.forEach(::println) } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-12.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-12.kt index ffcb368f565..015eeff8196 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-12.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-12.kt @@ -14,18 +14,13 @@ suspend fun closeFile(file: File): Unit = file.close() suspend fun fileToString(file: File): String = file.toString() suspend fun main(): Unit { - val res: List = listOf( - "data.json", - "user.json", - "resource.json" - ).map { uri -> - resource { - openFile(uri) - } release { file -> - closeFile(file) - } - }.sequence().use { files -> - files.map { fileToString(it) } + val res = resource { + openFile("data.json") + } release { file -> + closeFile(file) + } use { file -> + fileToString(file) } - res.forEach(::println) + + println(res) } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-13.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-13.kt new file mode 100644 index 00000000000..baa04722d84 --- /dev/null +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-13.kt @@ -0,0 +1,31 @@ +// This file was automatically generated from Resource.kt by Knit tool. Do not edit. +package arrow.fx.coroutines.examples.exampleResource13 + +import arrow.fx.coroutines.* + +class File(url: String) { + suspend fun open(): File = this + suspend fun close(): Unit {} + override fun toString(): String = "This file contains some interesting content!" +} + +suspend fun openFile(uri: String): File = File(uri).open() +suspend fun closeFile(file: File): Unit = file.close() +suspend fun fileToString(file: File): String = file.toString() + +suspend fun main(): Unit { + val res: List = listOf( + "data.json", + "user.json", + "resource.json" + ).traverse { uri -> + resource { + openFile(uri) + } release { file -> + closeFile(file) + } + }.use { files -> + files.map { fileToString(it) } + } + res.forEach(::println) +} diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-14.kt b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-14.kt new file mode 100644 index 00000000000..582729f63ce --- /dev/null +++ b/arrow-libs/fx/arrow-fx-coroutines/src/jvmTest/kotlin/examples/example-resource-14.kt @@ -0,0 +1,31 @@ +// This file was automatically generated from Resource.kt by Knit tool. Do not edit. +package arrow.fx.coroutines.examples.exampleResource14 + +import arrow.fx.coroutines.* + +class File(url: String) { + suspend fun open(): File = this + suspend fun close(): Unit {} + override fun toString(): String = "This file contains some interesting content!" +} + +suspend fun openFile(uri: String): File = File(uri).open() +suspend fun closeFile(file: File): Unit = file.close() +suspend fun fileToString(file: File): String = file.toString() + +suspend fun main(): Unit { + val res: List = listOf( + "data.json", + "user.json", + "resource.json" + ).map { uri -> + resource { + openFile(uri) + } release { file -> + closeFile(file) + } + }.sequence().use { files -> + files.map { fileToString(it) } + } + res.forEach(::println) +}