diff --git a/docs/content/docs/01-boundaries.md b/docs/content/docs/01-boundaries.md index a95aa3aa..26e3e204 100644 --- a/docs/content/docs/01-boundaries.md +++ b/docs/content/docs/01-boundaries.md @@ -40,6 +40,8 @@ But, most importantly, they **lay the foundations** (along with a **`resume` mec ## Modeling error handling data types with non-local breaks +[[Here you can find the full source](https://github.com/tassiLuca/direct-style-experiments/tree/master/commons/src/main/scala/io/github/tassiLuca/dse/boundaries).] + 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. @@ -64,8 +66,6 @@ object optional: ### `Either` + `?` -[[Here you can find the full source]()] - ```scala /** Represents a computation that will hopefully return a [[Right]] value but might fail with a [[Left]] one.*/ object either: diff --git a/docs/content/docs/02-basics.md b/docs/content/docs/02-basics.md index 00c5445c..66b6511e 100644 --- a/docs/content/docs/02-basics.md +++ b/docs/content/docs/02-basics.md @@ -89,13 +89,13 @@ To implement the service two components have been conceived, following the Cake - mocks a DB technology with an in-memory collection. - `PostsServiceComponent` - is the component exposing the `Service` interface. - - it would be called by the controller of the ReSTful web service. + - it could be called by the controller of the ReSTful web service. Both must be designed in an async way. ### Current monadic `Future` -The interface of the repository and services component of the monadic version are presented hereafter and their complete implementation is available [here](https://github.com/tassiLuca/PPS-22-direct-style-experiments/tree/master/blog-ws-monadic/src/main/scala/io/github/tassiLuca/dse/blog). +The interface of the repository and services component of the monadic version are presented hereafter and their complete implementation is available [here](https://github.com/tassiLuca/direct-style-experiments/tree/master/blog-ws-monadic/src/main/scala/io/github/tassiLuca/dse/blog). ```scala /** The component exposing blog posts repositories. */ @@ -147,7 +147,7 @@ All the exposed functions, since they are asynchronous, return an instance of `F What's important to delve into is the implementation of the service, and, more precisely, of the `create` method. As already mentioned, before saving the post two checks need to be performed: 1. the post author must have permission to publish a post and their information needs to be retrieved (supposing they are managed by another service); -2. the content of the post is analyzed in order to prevent the storage and publication of offensive or inappropriate content. +2. the content of the post is analyzed in order to prevent the storage and publication of inappropriate content. Since these operations are independent from each other they can be spawned and run in parallel. @@ -294,7 +294,7 @@ classDiagram {{< /mermaid >}} -Going back to our example, the interface of both the repository and service components becomes ([here](https://github.com/tassiLuca/PPS-22-direct-style-experiments/tree/master/blog-ws-direct/src/main/scala/io/github/tassiLuca/dse/blog) you can find the complete sources): +Going back to our example, the interface of both the repository and service components becomes ([here](https://github.com/tassiLuca/direct-style-experiments/tree/master/blog-ws-direct/src/main/scala/io/github/tassiLuca/dse/blog) you can find the complete sources): ```scala /** The component exposing blog posts repositories. */ @@ -353,7 +353,7 @@ The other important key feature of the library is the support for **structured c - `Future`s are `Cancellable` instances; - When you cancel a future using the `cancel()` method, it promptly sets its value to `Failure(CancellationException)`. Additionally, if it's a runnable future, the thread associated with it is interrupted using `Thread.interrupt()`. - - to avoid immediate cancellation, deferring the cancellation after some block is possible using `uninterruptible` function: + - to avoid immediate cancellation, deferring the cancellation after some block, is possible using `uninterruptible` function: ```scala val f = Future: @@ -367,7 +367,7 @@ The other important key feature of the library is the support for **structured c - 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: +The implementation of the `create` function with direct style in Gears looks like this: ```scala override def create(authorId: AuthorId, title: Title, body: Body)(using Async): Either[String, Post] = @@ -404,8 +404,8 @@ Some remarks: 👉🏻 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) +- [`StructuredConcurrencyTest`](https://github.com/tassiLuca/direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/dse/StructuredConcurrencyTest.scala) +- [`CancellationTest`](https://github.com/tassiLuca/direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/dse/CancellationTest.scala) Other combinator methods, available on `Future`s instance: @@ -471,7 +471,7 @@ Other combinator methods, available on `Future`s instance: - Coroutines follow the principle of structured concurrency: coroutines can be arranged into parent-child hierarchies where the cancellation of a parent leads to the immediate cancellation of all its children recursively. Failure of a child with an exception immediately cancels its parent and, consequently, all its other children. -Going back to our example, the interface of the service with Kotlin coroutines looks like this ([here](https://github.com/tassiLuca/PPS-22-direct-style-experiments/tree/master/blog-ws-direct-kt/src/main/kotlin/io/github/tassiLuca/dse/blog) you can find the complete sources): +Going back to our example, the interface of the service with Kotlin coroutines looks like this ([here](https://github.com/tassiLuca/direct-style-experiments/tree/master/blog-ws-direct-kt/src/main/kotlin/io/github/tassiLuca/dse/blog) you can find the complete sources): ```kotlin /** The service exposing a set of functionalities to interact with blog posts. */ @@ -509,17 +509,17 @@ 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. +- 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 - 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) + - have a look to [this test](https://github.com/tassiLuca/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 > - 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. +> - As [stated by M. Odersky](https://github.com/lampepfl/gears/issues/19#issuecomment-1732586362) the `Async` capability is better than `suspend` because let defines functions that work for synchronous as well as asynchronous function arguments, while suspending functions in Kotlin requires to be called from a coroutine. {{< button relref="/01-boundaries" >}} **Previous**: boundary & break{{< /button >}} diff --git a/docs/content/docs/03-channels.md b/docs/content/docs/03-channels.md index fbc7e68f..51eb917b 100644 --- a/docs/content/docs/03-channels.md +++ b/docs/content/docs/03-channels.md @@ -79,7 +79,7 @@ Similar behavior can be achieved also in Gears extending the framework with the {{< /hint >}} -[[The full implementation can be found in `commons` submodule](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/main/scala/io/github/tassiLuca/pimping/TerminableChannel.scala).] +[[The full implementation can be found in `commons` submodule, `pimping` package](https://github.com/tassiLuca/direct-style-experiments/blob/master/commons/src/main/scala/io/github/tassiLuca/dse/pimping/TerminableChannel.scala).] ```scala /** A token to be sent to a channel to signal that it has been terminated. */ @@ -139,7 +139,7 @@ object TerminableChannel: catch case _: NoSuchElementException => () ``` -Now, also in Scala with Gears is possible to write: +Now, thanks to this extension, also in Scala Gears is possible to write: ```scala val channel = TerminableChannel.ofUnbounded[Int] @@ -150,7 +150,7 @@ channel.foreach(println(_)) // blocks until channel is closed println("Done!") ``` -[[Other tests can be found in `TerminableChannelTest`]().] +[[Other tests can be found in `TerminableChannelTest`](https://github.com/tassiLuca/direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/dse/pimping/TerminableChannelTest.scala).] On top of this new abstraction is possible to implement, for example, the `foreach` and `toSeq` methods, which can be useful to wait for all the items sent over the channel. @@ -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/blog).] +[[The sources are available inside the `analyzer-monadic` submodule](https://github.com/tassiLuca/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. @@ -293,7 +293,7 @@ class MonadicAppController extends AppController: import monix.execution.Scheduler.Implicits.global private val view = AnalyzerView.gui(this) - private val analyzer = Analyzer.ofGitHub() + private val analyzer = Analyzer(RepositoryService.ofGitHub) private var currentComputation: Option[CancelableFuture[Unit]] = None view.run() @@ -311,7 +311,7 @@ class MonadicAppController extends AppController: ### Scala Gears version -[[The sources are available inside the `analyzer-direct` submodule](https://github.com/tassiLuca/PPS-22-direct-style-experiments/tree/master/analyzer-direct/src/main/scala/io/github/tassiLuca/analyzer).] +[[The sources are available inside the `analyzer-direct` submodule](https://github.com/tassiLuca/direct-style-experiments/tree/master/analyzer-direct/src/main/scala/io/github/tassiLuca/analyzer).] The interfaces of the Direct Style with Gears differ from the monadic one by their return type, which is a simpler `Either` data type, and by the fact they are **suspendable functions**, hence they require an Async context to be executed. This is the first important difference: the `analyze` method, differently from the monadic version, doesn't return immediately the control; instead, it suspends the execution of the client until the result is available (though offering the opportunity to react to each update). @@ -464,7 +464,7 @@ 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 sources are available inside the `analyzer-direct-kt` submodule](https://github.com/tassiLuca/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. @@ -553,7 +553,7 @@ They offer several useful operators for transforming and combining them function - `map` to transform the values; - `transform` to implement more complex transformations (possibly involving suspending operations); - `take` and its variant (e.g. `takeWhile`) to limit the number of values emitted; -- `onEach` to perform side-effects for each value emitted; +- `onEach` to perform side-effects for each value emitted. <---> @@ -562,16 +562,15 @@ They offer several useful operators for transforming and combining them function - conversions to various collection types, like `toList`, `toSet`; - `first`, `last`, `single` to retrieve the first, last or single value emitted; - `reduce` to perform some kind of operation over all items, reducing them to a single one; -- `fold` to perform some kind of operation over all items, starting from an initial value, accumulating a result; +- `fold` to perform some kind of operation over all items, starting from an initial value, accumulating a result. <---> *Flows combining operators*: - `merge` to combine multiple flows into a single one, emitting values from all of them; -- `zip` -- `combine` -- `flatMapConcat` / `flatMapMerge` to transform each value into a flow and then concatenate/merge them; +- `zip` combines the corresponding values of two flows; +- `flatMapConcat` / `flatMapMerge` to transform each value into a flow and then concatenate/merge them. {{< /columns >}} @@ -601,6 +600,25 @@ override suspend fun analyze( } ``` +Concerning flows, an important thing to note is that they are just asynchronous generators that run some suspending code when you collect them. Thus, per se, they don't introduce new coroutines or concurrency mechanism. +To achieve concurrency, for example emitting values concurrently by multiple coroutines, is necessary to use a `channelFlow`, a pre-cooked way to inject a `coroutineContext` and a `Channel` through which is possible to pass the values to be emitted from a background coroutines back to the main control flow: + +```kotlin +fun analyzeAll(repositories: List): Flow = channelFlow { + repositories.forEach { repository -> + launch { + val release = async { + provider.lastReleaseOf(repository.organization, repository.name).getOrThrow() + } + provider.flowingContributorsOf(repository.organization, repository.name).toList().forEach { + // emit this value + send(RepositoryReport(repository.name, repository.issues, repository.stars, it, release.await())) + } + } + } +} +``` + ## Introducing `Flow`s in Gears {{< hint info >}} @@ -615,7 +633,7 @@ The following section describes the attempt made to implement it and what has be - the behavior of the `emit` method is defined inside the `apply` method of `Flow` and injected inside caller code via the context parameter `(it: FlowCollector[T]) ?=>`. - Once the task has finished, the channel is terminated. -[[Source code can be found in `commons` submodule, `pimpimg` package](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/main/scala/io/github/tassiLuca/pimping/Flow.scala).] +[[Source code can be found in `commons` submodule, `pimpimg` package](https://github.com/tassiLuca/direct-style-experiments/blob/master/commons/src/main/scala/io/github/tassiLuca/dse/pimping/Flow.scala).] ```scala /** An asynchronous cold data stream that emits values, inspired to Kotlin Flows. */ @@ -663,7 +681,7 @@ object Flow: myChannel.foreach(t => collector(t)) ``` -`map` and `flatMap` have been implemented on top of `Flow`: +`map` and `flatMap` combinators have been implemented on top of `Flow`: ```scala object FlowOps: @@ -687,7 +705,7 @@ object FlowOps: ### Showcasing `Flow`s -Library use cases: +Library use case: ```scala type Name = String @@ -804,13 +822,13 @@ 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/direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/dse/pimping/FlowTest.scala). ## Takeaways > - `Channel`s are the basic communication and synchronization primitive for exchanging data between `Future`s/`Coroutine`s. > - 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**. +> - 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 *upon 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 relref="/02-basics" >}} **Previous**: Basic asynchronous constructs{{< /button >}} diff --git a/docs/content/docs/04-rears.md b/docs/content/docs/04-rears.md index 3b3ba631..9e5bf5c4 100644 --- a/docs/content/docs/04-rears.md +++ b/docs/content/docs/04-rears.md @@ -51,7 +51,7 @@ classDiagram {{< hint warning >}} -**Warning**: with high-order functions if we deal with repeated `Tasks`, in some cases an `Async ?=>` label is required to not suspend the whole block, even if a suspending operation is performed: the code below *behaves differently* if the `Async ?=>` label is present or not. Note: this may be an unintended effect of the library, yet to be investigated. +**Warning**: with high-order functions if we deal with repeated `Tasks`, in some cases an `Async ?=>` label is required to not suspend the whole block, even if a suspending operation is performed: the code below *behaves differently* if the `Async ?=>` label is present or not. Note: this may be an unintended effect of the library, yet to be investigated (sometimes on Ubuntu it doesn't work, suggesting to be a "bug", see [here](https://github.com/tassiLuca/direct-style-experiments/actions/runs/8147260065/job/22267570282?pr=24#step:4:434) vs. [here](https://github.com/tassiLuca/direct-style-experiments/actions/runs/8147339495/job/22267819794#step:4:432)) {{< /hint >}} @@ -103,7 +103,7 @@ def produceWithLabel[T]( {{< /columns >}} -[[See the tests for more details.](https://github.com/tassiLuca/PPS-22-direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/TasksTest.scala#L27)] +[[See the tests for more details.](https://github.com/tassiLuca/direct-style-experiments/blob/master/commons/src/test/scala/io/github/tassiLuca/dse/TasksTest.scala)] - To avoid the _work-stealing behavior_ of channel consumers, a `ChannelMultiplexer` can be used. It is essentially a container of `Readable` and `Sendable` channels, which can be added and removed at runtime. Internally, it is implemented with a thread that continuously races the set of publishers and once it reads a value, it forwards it to each subscriber channel. - Order is guaranteed only per producer; @@ -135,7 +135,7 @@ In the proposed strawman Scala Gears library, there are no other kinds of abstra The attempt, described in the following, has been to extend this framework adding first-class support for `Producer` and `Consumer`'s concepts and implementing some of the most common Rx operators, completely leaving out performance concerns. -[[Sources can be found in the `rears` submodule.]](https://github.com/tassiLuca/PPS-22-direct-style-experiments/tree/master/rears/src/main/scala/io/github/tassiLuca/rears) +[[Sources can be found in the `rears` submodule.]](https://github.com/tassiLuca/direct-style-experiments/tree/master/rears/src/main/scala/io/github/tassiLuca/rears). - A `Producer` is a runnable entity, programmed with a `Task`, producing items on a channel. It exposes the `publishingChannel` method, which returns a `ReadableChannel` through which interested consumers can read produced items. - A `Consumer` is a runnable entity devoted to consuming data from a channel, exposed by the `listeningChannel` method which returns a `SendableChannel` to send items to. @@ -554,8 +554,9 @@ suspend fun run(sensorSource: Flow) { ## 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... - +- Channels in Scala Gears are good to model flow of data **that exist without application's request from them**: incoming network connections, event streams, etc... +- The scheduling mechanism of Task, along with the mutiplexer abstraction, despite having some stability issues, allows to implement flows of **hot** data which are listened by multiple consumers. +- Transformation operators inspired by the Reactive world could enhance the expressiveness of the framework, making it more suitable for modeling reactive event-based systems. {{< button relref="/03-channels" >}} **Previous**: Channels as a communication primitive{{< /button >}} diff --git a/docs/content/docs/05-conclusions.md b/docs/content/docs/05-conclusions.md index 683196b6..cb15096c 100644 --- a/docs/content/docs/05-conclusions.md +++ b/docs/content/docs/05-conclusions.md @@ -2,16 +2,13 @@ bookToc: false --- -# Going further and conclusions +# Conclusions and Final Considerations -## Recap +In conclusion, this project analyzed the main direct asynchronous programming abstractions offered by Kotlin's Coroutines and Scala's new, still entirely experimental library proposal, Gears. +During the analysis, input was also provided for its possible extension where it was deemed lacking compared to the Kotlin Coroutines framework. -- `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. +Despite being a very young project, created for experimenting with direct style in Scala, it already offers a good set of abstractions for asynchronous programming, and seems to be promising for the future. -## 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. +In general, the direct style is a very interesting approach and it is worth exploring further in the context of asynchronous programming, where it can offer a more natural and intuitive way to handle concurrency with respect to monadic transformations, which require a more complex model to be fully understood and grasped. {{< button relref="/" >}} **Home** {{< /button >}}