diff --git a/docs/content/_index.md b/docs/content/_index.md index 4a0b7a76..19e967a3 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -27,5 +27,3 @@ Here's the outline of the conducted analysis: 5. [Conclusions](./docs/05-going-further) Code has been organized in Gradle submodules, one for each version of the examples (current monadic futures, Scala Gears, Kotlin Coroutines): - - diff --git a/docs/content/docs/01-boundaries.md b/docs/content/docs/01-boundaries.md index 17d5e46e..0305781a 100644 --- a/docs/content/docs/01-boundaries.md +++ b/docs/content/docs/01-boundaries.md @@ -4,15 +4,19 @@ bookToc: false # `boundary` & `break` -[[Source code](https://github.com/lampepfl/dotty/blob/3.3.0-RC4/library/src/scala/util/boundary.scala)] +- [`boundary` \& `break`](#boundary--break) + - [Modeling error handling data types with non-local breaks](#modeling-error-handling-data-types-with-non-local-breaks) + - [`Optional`](#optional) + - [`Either` + `?`](#either--) `boundary` & `break` mechanism provides a cleaner alternative to non-local returns: - `boundary:` is short for `boundary.apply:` -- the indented code below it is passed as `body` is a context function that is called within `boundary.apply` - - to `break` an in-scope `given` instance of `Label` is required (i.e. is impossible `break` without an enclosing `boundary`) - - Users don't define `Label` instances themselves. Instead, this is done inside the implementation of `boundary.apply` to provide the capability of doing a non-local return +- the indented code below, passed as `body`, is a context function that is called within `boundary.apply` + - to `break`, an in-scope `given` instance of `Label` is required (i.e. is impossible to `break` without an enclosing `boundary`) + - Users don't define `Label` instances themselves. Instead, this is done inside the implementation of `boundary.apply` to provide the capability of doing a non-local return [[Ref](https://github.com/lampepfl/dotty/blob/3.3.0-RC4/library/src/scala/util/boundary.scala)]: ```scala + // From the Scala 3 standard library /** Run `body` with freshly generated label as implicit argument. * Catch any breaks associated with that label and return their * results instead of `body`'s result. @@ -34,9 +38,11 @@ But, most importantly, they **lay the foundations** (along with a **`resume` mec {{< /hint >}} - ## Modeling error handling data types with non-local breaks +In the following section are presented two data types that can be used to handle errors, both leveraging the `boundary` and `break` mechanism. +The first (`optional`) has been presented in the [Scalar conference by M. Odersky](https://www.google.com/search?client=safari&rls=en&q=direct+style+odersky&ie=UTF-8&oe=UTF-8), while the second has been implemented to apply the same style also to `Either` data type. + ### `Optional` ```scala @@ -59,7 +65,7 @@ object optional: ### `Either` + `?` ```scala -/** Represents a computation that will hopefully return a [[Right]] value, but might fail with a [[Left]] one. */ +/** Represents a computation that will hopefully return a [[Right]] value but might fail with a [[Left]] one.*/ object either: /** Defines the boundary for the [[Either]] returning computation, whose [[body]] is given in input. */ @@ -76,12 +82,13 @@ object either: case Left(value) => break(Left(value)) extension [R](t: Try[R]) - /** @return this [[Success]] value or break to the enclosing boundary with a [[Left]] containing the converted - * `Throwable` exception performed by the implicit [[converter]]. + /** @return this [[Success]] value or break to the enclosing boundary with a [[Left]] containing + * the converted `Throwable` exception performed by the implicit [[converter]]. */ - inline def ?[L](using Label[Left[L, Nothing]])(using converter: Conversion[Throwable, L]): R = t match - case Success(value) => value - case Failure(exception) => break(Left(converter(exception))) + inline def ?[L](using Label[Left[L, Nothing]])(using converter: Conversion[Throwable, L]): R = + t match + case Success(value) => value + case Failure(exception) => break(Left(converter(exception))) /** An object encapsulating a collection of `Throwable` given converters. */ object EitherConversions: @@ -91,3 +98,7 @@ object EitherConversions: ``` This kind of data type will be particularly useful in the next examples to quickly break in case of failures, returning the caller a meaningful error message, and simplifying the error-handling code. + +{{< button href="https://tassiluca.github.io/PPS-22-direct-style-experiments/PPS-22-direct-style-experiments/" >}} **Home** {{< /button >}} + +{{< button href="https://tassiluca.github.io/PPS-22-direct-style-experiments/PPS-22-direct-style-experiments/docs/02-basics/" >}} **Next**: Basic asynchronous constructs {{< /button >}} diff --git a/docs/content/docs/02-basics.md b/docs/content/docs/02-basics.md index ce06be3e..e8fcbac2 100644 --- a/docs/content/docs/02-basics.md +++ b/docs/content/docs/02-basics.md @@ -363,7 +363,8 @@ The other important key feature of the library is the support for **structured c // this can be interrupted ``` -- `Future`s are nestable; **the lifetime of nested computations is contained within the lifetime of enclosing ones**. This is achieved using `CompletionGroup`s, which are cancellable objects themselves and serve as containers for other cancellable objects, that once canceled, all of its members are canceled as well. +- `Future`s are nestable; **the lifetime of nested computations is contained within the lifetime of enclosing ones**. This is achieved using **`CompletionGroup`s**, which are cancellable objects themselves and serve as **containers for other cancellable objects**; **once they are canceled, all of their members are canceled as well**. + - The group is accessible through `Async.current.group`; - A cancellable object can be included inside the cancellation group of the async context using the `link` method; this is what the [implementation of the `Future` does, under the hood](https://github.com/lampepfl/gears/blob/07989ffdae153b2fe11ac1ece53ce9dd1dbd18ef/shared/src/main/scala/async/futures.scala#L140). The implementation of the `create` function with direct style in gears looks like this: @@ -401,9 +402,10 @@ Some remarks: val (post, author) = contentVerifier.zip(authorizer).awaitResult.? ``` - 👉🏻 To showcase the structured concurrency and cancellation mechanisms of Scala Gears tests have been prepared: - - [`StructuredConcurrencyTest`](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/StructuredConcurrencyTest.scala) - - [`CancellationTest`](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/CancellationTest.scala) +👉🏻 To showcase the structured concurrency and cancellation mechanisms of Scala Gears tests have been prepared: + +- [`StructuredConcurrencyTest`](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/dse/StructuredConcurrencyTest.scala) +- [`CancellationTest`](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/dse/CancellationTest.scala) Other combinator methods, available on `Future`s instance: @@ -435,17 +437,17 @@ Other combinator methods, available on `Future`s instance: } ``` - Every coroutine must be executed in a coroutine context which is a collection of key-value pairs that provide contextual information for the coroutine, including a dispatcher, that determines what thread or threads the coroutine uses for its execution, and the `Job` of the coroutine, which represents a cancellable background piece of work with a life cycle that culminates in its completion. + Every coroutine must be executed in a coroutine context, which is a collection of key-value pairs that provide contextual information for the coroutine, including a dispatcher, that determines what thread or threads the coroutine uses for its execution, and the `Job` of the coroutine, which represents a cancellable background piece of work with a life cycle that culminates in its completion. - Different ways to create a scope: - - `GlobalScope.launch` launching a new coroutine in the global scope -- *discouraged because it can lead to memory leaks* - - `CoroutineScope(Dispatchers.Default)`, using the constructor with a dispatcher - - `runBlocking` - equivalent to the Gears `Async.blocking` - provides a way to run a coroutine in the `MainScope`, i.e. on the main/UI thread + - `GlobalScope.launch` launching a new coroutine in the global scope -- *discouraged because it can lead to memory leaks*; + - `CoroutineScope(Dispatchers.Default)`, using the constructor with a dispatcher; + - `runBlocking` - equivalent to the Gears `Async.blocking` - provides a way to run a coroutine in the `MainScope`, i.e. on the main/UI thread. - Useful dispatchers: - - `Default dispatcher`: to run CPU-intensive functions. If we forget to choose our dispatcher, this dispatcher will be selected by default. - - `IO dispatcher`: to run I-O bound computation, where we block waiting for input-output operations to complete, like network-related operations, file operations, etc. - - `Unconfined dispatcher`: it isn't restricted to a particular thread, i.e. doesn't change the thread of the coroutine, it operates on the same thread where it was initiated. + - `Default dispatcher`: to run CPU-intensive functions. If we forget to choose our dispatcher, this dispatcher will be selected by default; + - `IO dispatcher`: to run I-O bound computation, where we block waiting for input-output operations to complete, like network-related operations, file operations, etc.; + - `Unconfined dispatcher`: it isn't restricted to a particular thread, i.e. doesn't change the thread of the coroutine, it operates on the same thread where it was initiated; - `Main dispatcher`: used when we want to interact with the UI. It is restricted to the main thread. - Several coroutine builders exist, like `launch`, `async`, `withContext` which accept an optional `CoroutineContext` parameter that can be used to specify the dispatcher and other context elements. @@ -508,7 +510,9 @@ private suspend fun verifyContent(title: String, body: String): PostContent { .. - a `coroutineScope` is a suspending function used to create a new coroutine scope: it suspends the execution of the current coroutine, releasing the underlying thread for other usages; - As we said previously, the failure of a child with an exception immediately cancels its parent and, consequently, all its other children: this means that, for handling the cancellation of nested coroutines, we don't need to do anything special, it is already automatically handled by the library. - - [No matter the order in which coroutines are awaited, if one of them fails all the others get cancelled](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/test/kotlin/io/github/tassiLuca/dse/CancellationTest.kt) + - with `coroutineScope` no matter the order in which coroutines are awaited, if one of them fails with an exception it is propagated upwards, cancelling all other ones + - this is not the case for `supervisorScope`, a coroutine builder ensuring that child coroutines can fail independently without affecting the parent coroutine. + - have a look to [this test](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/blog-ws-direct-kt/src/test/kotlin/io/github/tassiLuca/dse/CoroutinesCancellationTests.kt) - This is an advantage over the Scala Gears, where operators like `zip` and `altWithCancel` are necessary! ## Takeaways @@ -516,3 +520,7 @@ private suspend fun verifyContent(title: String, body: String): PostContent { .. > - Scala Gears offers, despite the syntactical differences, very similar concepts to Kotlin Coroutines, with structured concurrency and cancellation mechanisms; > - Kotlin Coroutines handles the cancellation of nested coroutines more easily than Scala Gears, where special attention is required; > - As [stated by M. Odersky](https://github.com/lampepfl/gears/issues/19#issuecomment-1732586362) the `Async` capability is better than `suspend` in Kotlin because let defines functions that work for synchronous as well as asynchronous function arguments. + +{{< button href="https://tassiluca.github.io/PPS-22-direct-style-experiments/PPS-22-direct-style-experiments/docs/01-boundaries" >}} **Previous**: boundary & break{{< /button >}} + +{{< button href="https://tassiluca.github.io/PPS-22-direct-style-experiments/PPS-22-direct-style-experiments/docs/03-channels/" >}} **Next**: Channels as a communication primitive {{< /button >}} diff --git a/docs/content/docs/03-channels.md b/docs/content/docs/03-channels.md index f514e2b9..00cdce40 100644 --- a/docs/content/docs/03-channels.md +++ b/docs/content/docs/03-channels.md @@ -218,7 +218,7 @@ As usual, it has been implemented using monadic `Future`s, as well as using Scal ### Future monadic version -[[The sources are available inside the `analyzer-monadic` submodule](https://github.com/tassiLuca/PPS-22-direct-style-experiments/tree/master/blog-ws-monadic/src/main/scala/io/github/tassiLuca/dse/blogv).] +[[The sources are available inside the `analyzer-monadic` submodule](https://github.com/tassiLuca/PPS-22-direct-style-experiments/tree/master/blog-ws-monadic/src/main/scala/io/github/tassiLuca/dse/blog).] The entry point of the library is the `Analyzer` interface which takes in input the organization name and a function through which is possible to react to results while they are computed. @@ -464,6 +464,8 @@ or having set an environment variable named `GH_TOKEN`. ### Kotlin Coroutines version +[[The sources are available inside the `analyzer-direct-kt` submodule](https://github.com/tassiLuca/PPS-22-direct-style-experiments/tree/master/analyzer-direct-kt/src/main/kotlin/io/github/tassiLuca/analyzer).] + The analyzer interface reflects the Scala Gears one: a `Result` is used in place of `Either`, and the suspendable function `udateResults` is marked with the `suspend` keyword in place of the `using Async` context. ```kotlin @@ -802,7 +804,7 @@ Success(The Tell-Tale Heart) {{< /columns >}} -[More tests on `Flows` can be found in `commons`, `pimping` pakcage](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/pimping/FlowTest.scala). +👉🏻 [More tests on `Flows` can be found in `commons`, `pimping` pakcage](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/pimping/FlowTest.scala). ## Takeaways @@ -810,3 +812,7 @@ Success(The Tell-Tale Heart) > - Scala Gears support for `Terminable` channels or a review of the closing mechanism should be considered. > - The `Flow` abstraction in Kotlin Coroutines is a powerful tool for handling cold streams of data, and it is a perfect fit for functions that need to return a stream of asynchronously computed values **by request**. > - A similar abstraction can be implemented in Scala Gears leveraging `Task`s and `TerminableChannel`s, enabling improved support for an asynchronous flow of data also in Gears, which is currently lacking. + +{{< button href="https://tassiluca.github.io/PPS-22-direct-style-experiments/PPS-22-direct-style-experiments/docs/02-basics" >}} **Previous**: Basic asynchronous constructs{{< /button >}} + +{{< button href="https://tassiluca.github.io/PPS-22-direct-style-experiments/PPS-22-direct-style-experiments/docs/04-rears" >}} **Next**: Reactivity in direct style{{< /button >}} diff --git a/docs/content/docs/04-rears.md b/docs/content/docs/04-rears.md index 4d77ee59..6f1a6509 100644 --- a/docs/content/docs/04-rears.md +++ b/docs/content/docs/04-rears.md @@ -548,4 +548,11 @@ suspend fun run(sensorSource: Flow) { } ``` -## Conclusions +## Takeaways + +- Channels in Scala Gears are fine to model flow of data **that exist without application's request from them**: incoming network connections, event streams, etc... +- + +{{< button href="https://tassiluca.github.io/PPS-22-direct-style-experiments/PPS-22-direct-style-experiments/docs/03-channels" >}} **Previous**: Channels as a communication primitive{{< /button >}} + +{{< button href="https://tassiluca.github.io/PPS-22-direct-style-experiments/PPS-22-direct-style-experiments/docs/05-conclusions" >}} **Next**: Conclusions{{< /button >}} diff --git a/docs/content/docs/05-conclusions.md b/docs/content/docs/05-conclusions.md index 2374a901..5506ed54 100644 --- a/docs/content/docs/05-conclusions.md +++ b/docs/content/docs/05-conclusions.md @@ -6,13 +6,12 @@ bookToc: false ## Recap -- `Channel`s are the basic communication primitive for exchanging data between `Future`s/`Coroutines` and they are primarily used to model data sources that are *intrinsically hot*, i.e. **that exist without application's request from them**: incoming network connections, event streams, etc... +- `Channel`s are the basic communication primitive for exchanging data between `Future`s/`Coroutines` and they are primarily used to model data sources that are *intrinsically hot*, i.e. - `Flow`s are control structures, containing executable code. When we call the `collect` method we invoke the code inside the flow, like executing function's code by calling it. - - ## Conclusions and Final Considerations To conclude, in this project, the main direct-style asynchronous programming abstractions offered by the Kotlin Coroutines and Scala's new, still entirely experimental library proposal, Gears, were analyzed. During the analysis, input was also provided for its possible extension where it was deemed lacking compared to the Kotlin framework. +{{< button href="https://tassiluca.github.io/PPS-22-direct-style-experiments/PPS-22-direct-style-experiments/" >}} **Home** {{< /button >}}