Skip to content

Commit

Permalink
docs: final version
Browse files Browse the repository at this point in the history
  • Loading branch information
tassiluca committed Mar 4, 2024
1 parent f7e734b commit 53c376e
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 44 deletions.
4 changes: 2 additions & 2 deletions docs/content/docs/01-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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:
Expand Down
24 changes: 12 additions & 12 deletions docs/content/docs/02-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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:
Expand All @@ -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] =
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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 >}}

Expand Down
52 changes: 35 additions & 17 deletions docs/content/docs/03-channels.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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]
Expand All @@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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()
Expand All @@ -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).
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.

<--->

Expand All @@ -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 >}}

Expand Down Expand Up @@ -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<Repository>): Flow<RepositoryReport> = 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 >}}
Expand All @@ -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. */
Expand Down Expand Up @@ -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:
Expand All @@ -687,7 +705,7 @@ object FlowOps:

### Showcasing `Flow`s

Library use cases:
Library use case:

```scala
type Name = String
Expand Down Expand Up @@ -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 >}}
Expand Down
Loading

0 comments on commit 53c376e

Please sign in to comment.