Skip to content

Commit

Permalink
docs: add overview page with requested modifications
Browse files Browse the repository at this point in the history
  • Loading branch information
tassiluca committed Mar 7, 2024
1 parent 4f937a3 commit e61044e
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 89 deletions.
25 changes: 5 additions & 20 deletions docs/content/_index.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
# Direct style for Functional Reactive Programming: an analysis in Scala & Kotlin

## Goals of the project

> In the realm of asynchronous programming, the Scala ecosystem offers a set of solid monads constructs and libraries to tackle complex task functionally with elegance and efficiency, like [Monix Tasks](https://monix.io/docs/current/eval/task.html) and [Cats Effect](https://typelevel.org/cats-effect/).
>
> However, we are assisting to the increase in adoption of continuation and coroutines in modern runtimes, either exploiting some kind of fibers support, like the project Loom with Virtual Threads, or via code generation, like Kotlin Coroutines.
>
> The goal of this project is to delve into this field through the lens of direct style, developing few examples (not too complex) leveraging the new *strawman* library [Scala Gears](https://github.com/lampepfl/gears), comparing it with Kotlin's Coroutines and the current implementation of monadic Futures, seeking to analyze aspects such as:
>
> - ergonomics of the two styles (which one results more thoughtful and/or verbose);
> - which of the two approaches has a real advantage in adoption;
> - pros and cons of the two styles;
> - any limitations and difficulties encountered in using them.
## Overview

The project is built around three main examples, delving from the fundamentals of the direct style frameworks for simple asynchronous computation to more complex reactive-like systems.

Here's the outline of the conducted analysis:

1. [`Boundary` and `break`](./docs/01-boundaries)
2. [Basic asynchronous constructs](./docs/02-basics)
3. [Channels as a communication primitive](./docs/03-channels)
4. [Reactivity in direct style](./docs/04-rears)
5. [Conclusions](./docs/05-going-further)
1. [Overview of the project: goals, contribution, and conclusions](./docs/01-overview)
2. [`Boundary` and `break`](./docs/02-boundaries)
3. [Basic asynchronous constructs](./docs/03-basics)
4. [Channels as a communication primitive](./docs/04-channels)
5. [Reactivity in direct style](./docs/05-rears)

Code has been organized in Gradle submodules, one for each version of the examples (current monadic futures, Scala Gears, Kotlin Coroutines).
Here an overview of the project folder structure:
Expand Down
39 changes: 0 additions & 39 deletions docs/content/docs/00-overview.md

This file was deleted.

81 changes: 81 additions & 0 deletions docs/content/docs/01-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Overview of the project

## Context

In the realm of asynchronous programming, the Scala ecosystem offers a set of solid and widely adopted monadic constructs and libraries to tackle complex tasks functionally with elegance and efficiency, like [Monix Tasks](https://monix.io/docs/current/eval/task.html) and [Cats Effecs](https://typelevel.org/cats-effect/), enabling a wide range of interesting and useful features, like composable error handling, cancellation mechanisms and structured concurrency that the standard library lacks.
However, they also come with a cost: the pervasiveness of the `flatMap` operator to compose values makes the code harder to reason about and difficult and awkward to integrate with regular control structures.

In the last years, we have been assisting the increase in adoption of continuation and coroutines in modern runtimes, either exploiting some kind of fibers support, like the project Loom with Virtual Threads, or via code generation, like Kotlin Coroutines, aiming to capture the essence of effects more cleanly compared to monads.

## Goals

The goal of this project is to explore, mainly focusing on Scala, the direct style, developing a few examples (not too complex) leveraging the new strawman library [Gears](https://github.com/lampepfl/gears), comparing it with [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) and the current implementation of monadic Futures, seeking to analyze aspects such as:

- ergonomics of the two styles;
- which of the two approaches has a real advantage in adoption;
- pros and cons of the two styles;
- any limitations and difficulties encountered in using them.

## The contribution

The project is built around three small examples.

The [first](../03-basics) aims, through the implementation of the core of a small Web service, to introduce the basics of asynchronous programming in direct style, focusing on structured concurrency and cancellation.

The [second](../04-channels) introduces the main communication and synchronization primitive of direct style, channels, and how they can be used to exchange data between concurrent tasks (either `Future`s or coroutines).

In this context, a contribution has been made by proposing the extension of the Scala Gears library with the following two abstractions, currently missing, inspired by those of the Kotlin Coroutines:

- **terminable channels**, i.e. a channel that can be terminated, but whose values can still be read by consumers after its termination until all values are consumed;
- **`Flow`s**, modeling a *cold* stream of asynchronously computed values which are particularly useful for implementing asynchronous functions that produce, not just a single, but a sequence of values.

The [last example](../05-rears) investigates how to implement a reactive-like event-based system using direct style, taking as a use case a small sensor control system in an IoT context that needs to react to events coming from different sensors.

To accomplish this, since Scala Gears lacks any kind of transforming operator, some have been introduced on top of channels (taking cues from Reactive frameworks) allowing them to be manipulated functionally.

## Conclusions

The current implementation of monadic Futures present in the standard library is insufficient to handle modern asynchronous programming in a structured and safe way: it lacks structured concurrency and cancellation mechanism, is not referential transparent and requires to be ugly mixed with direct style constructs to be used appropriately.

This leads to the adoption of effectful libraries that offer these and many other powerful abstractions, beautifully wrapped in monadic constructs.
This is the main pro and con of the monadic approach: what makes monads remarkable is their capability to turn statements into programmable values and introduce constructs to transform and compose them functionally in a very elegant (and somehow "magical" for ones who are not familiar with them) way.
Despite this idyllic beauty, monads and effectful libraries require relevant expertise in functional programming to be fully grasped and composed effectively.

Direct style frameworks are indeed arising as a more natural and intuitive way to handle concurrency, leveraging an imperative-like programming style that is familiar to all developers.

In the JVM ecosystem, the most adopted and known direct-style library is the Kotlin Coroutines, which were introduced in 2018 by modifying the Kotlin language (rather than its runtime, like the project Loom has recently done with Virtual Thread) to support suspending functions.
The main advantages of Kotlin Coroutines are that they provide suspension and cancellation mechanisms that are simple to understand and use, as well as a good ecosystem for channels and `Flow`s.
Despite this, Coroutines are not still perfect: due to their design they partially suffer from the [colored functions problem](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/) and we need to be aware we can not use the same synchronization concurrency abstractions that we would use in Java with threads (like locks, `synchronized`, ...) cause they are not designed to be used in the coroutines context.

Scala Gears is an attempt to bring direct style into the Scala ecosystem.
Its API design is inspired by Kotlin Coroutines and, despite the fact it achieves suspension, unlike Kotlin, leveraging Virtual Threads for JVM and delimited continuation for Scala Native, the majority of constructs can be mapped to the Kotlin Coroutines ones.
Despite being a very young project, it already offers a good set of abstractions for asynchronous programming, although it cannot yet be considered a mature library ready to be used in a production environment:

- some design choices should be addressed:
- closing a channel prevents any further reading, precluding the possibility of processing the remaining values (see [second](../04-channels) example);
- Task scheduling behavior has a strange behavior with higher-order functions (see [third](../05-rears) example);
- missing abstractions: the library is still missing some important abstractions, like the proposed `Flow`s for handling a cold stream of asynchronously computed values and operators for functionally transforming them;
- performances: the project has been created for experimenting, thus performances have not been considered a priority so far, [even though a comparison in overheads of
the core primitives has been published](https://github.com/lampepfl/gears/blob/main/docs/summary-2023-06.md#performance).

In conclusion, Scala Gears is a promising project that could bring direct-style async programming into the Scala ecosystem, giving, together with boundary and break Scala 3 support, a nice alternative to the current monadic approach, simplifying the way we handle concurrency, making it more natural and intuitive.

## References

- [Scala Gears, Programming Methods Laboratory EPFL](https://github.com/lampepfl/gears/tree/main/docs)
- [Scala 3: What Is "Direct Style" by D. Wampler](https://medium.com/scala-3/scala-3-what-is-direct-style-d9c1bcb1f810#:~:text=Dean%20Wampler-,Scala%203,without%20the%20boilerplate%20of%20monads.)
- [Kotlin Coroutines documentation](https://kotlinlang.org/docs/coroutines-overview.html)
- [Pre-SIP: Suspended functions and continuations in Scala 3](https://contributors.scala-lang.org/t/pre-sip-suspended-functions-and-continuations/5801/20?u=adamw)
- [The Great Concurrency Smackdown: ZIO versus JDK by John A. De Goes](https://www.youtube.com/watch?v=9I2xoQVzrhs)
- [Continuaton, coroutine, and generator by A. Ber](https://medium.com/geekculture/continuation-coroutine-continuation-generator-9a1af03a3bed)
- [KotlinConf 2017 - Introduction to Coroutines by Roman Elizarov](https://www.youtube.com/watch?v=_hfBv0a09Jc)
- [KotlinConf 2017 - Deep Dive into Coroutines on JVM by Roman Elizarov](https://www.youtube.com/watch?v=YrrUCSi72E8&t=42s)
- [Kotlin Co-routine Scope by A. Nadiger](https://www.linkedin.com/pulse/kotlin-co-routine-scope-amit-nadiger#:~:text=CoroutineScope%20is%20an%20interface%20in,of%20the%20CoroutineScope%20have%20completed.)
- [What Color is your function by B. Nystrom](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/)
- [Channel in Kotlin Coroutines](https://kt.academy/article/cc-channel)
- [SharedFlow and StateFlow](https://kt.academy/article/cc-sharedflow-stateflow)
- [Demystifying Kotlin's Channel Flows by S. Cooper](https://betterprogramming.pub/demystifying-kotlins-channel-flows-b9007e1f773b)

{{< button relref="/" >}} **Home** {{< /button >}}

{{< button relref="/02-boundaries" >}} **Next**: boundary & break {{< /button >}}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,6 @@ 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 relref="/" >}} **Home** {{< /button >}}
{{< button relref="/01-overview" >}} **Overview** {{< /button >}}

{{< button relref="/02-basics" >}} **Next**: Basic asynchronous constructs {{< /button >}}
{{< button relref="/03-basics" >}} **Next**: Basic asynchronous constructs {{< /button >}}
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,8 @@ 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` because let defines functions that work for synchronous as well as asynchronous function arguments, while suspending functions in Kotlin require to be called from a coroutine.
> - 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 without changing anything, while in Kotlin suspendable functions passed as an argument in higher-order functions must be tagged with `suspend` keyword.
{{< button relref="/01-boundaries" >}} **Previous**: boundary & break{{< /button >}}
{{< button relref="/02-boundaries" >}} **Previous**: boundary & break{{< /button >}}

{{< button relref="/03-channels" >}} **Next**: Channels as a communication primitive {{< /button >}}
{{< button relref="/04-channels" >}} **Next**: Channels as a communication primitive {{< /button >}}
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ trait TerminableChannel[T] extends Channel[Terminable[T]]:
object TerminableChannel:

/** Creates a [[TerminableChannel]] backed to [[SyncChannel]]. */
def ofSync[T: ClassTag]: TerminableChannel[T] = TerminableChannelImpl(SyncChannel())
def ofSync[T]: TerminableChannel[T] = TerminableChannelImpl(SyncChannel())

/** Creates a [[TerminableChannel]] backed to [[BufferedChannel]]. */
def ofBuffered[T: ClassTag]: TerminableChannel[T] = TerminableChannelImpl(BufferedChannel())
def ofBuffered[T]: TerminableChannel[T] = TerminableChannelImpl(BufferedChannel())

/** Creates a [[TerminableChannel]] backed to an [[UnboundedChannel]]. */
def ofUnbounded[T: ClassTag]: TerminableChannel[T] = TerminableChannelImpl(UnboundedChannel())
def ofUnbounded[T]: TerminableChannel[T] = TerminableChannelImpl(UnboundedChannel())

private class TerminableChannelImpl[T: ClassTag](c: Channel[Terminable[T]]) extends TerminableChannel[T]:
private class TerminableChannelImpl[T](c: Channel[Terminable[T]]) extends TerminableChannel[T]:
opaque type Res[R] = Either[Channel.Closed, R]

private var _terminated: Boolean = false
Expand All @@ -134,9 +134,9 @@ object TerminableChannel:

override def terminate()(using Async): Unit =
try send(Terminated)
// It happens only at the close of the channel due to the call (inside Gears
// library) of a CellBuf.dequeue(channels.scala:239) which is empty!
catch case _: NoSuchElementException => ()
// It happens only at the close of the channel due to the call (inside Gears library) of
// a CellBuf.dequeue(channels.scala:239) which is empty!
catch case _: NoSuchElementException => () // e.printStackTrace()
```

Now, thanks to this extension, also in Scala Gears is possible to write:
Expand Down Expand Up @@ -213,6 +213,8 @@ listing their information along with all their contributors as soon as they are

![expected result](../../res/img/analyzer-e2e.png)

[This example has been inspired by [this](https://kotlinlang.org/docs/coroutines-and-channels.html) tutorial.]

---

To start the application:
Expand Down Expand Up @@ -829,6 +831,6 @@ Success(The Tell-Tale Heart)
> - 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 >}}
{{< button relref="/03-basics" >}} **Previous**: Basic asynchronous constructs{{< /button >}}

{{< button relref="/04-rears" >}} **Next**: Reactivity in direct style{{< /button >}}
{{< button relref="/05-rears" >}} **Next**: Reactivity in direct style{{< /button >}}
14 changes: 0 additions & 14 deletions docs/content/docs/05-conclusions.md

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,6 @@ suspend fun run(sensorSource: Flow<TemperatureEntry>) {
- 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 >}}
{{< button relref="/04-channels" >}} **Previous**: Channels as a communication primitive{{< /button >}}

{{< button relref="/05-conclusions" >}} **Next**: Conclusions{{< /button >}}
{{< button relref="/" >}} **Home**{{< /button >}}

0 comments on commit e61044e

Please sign in to comment.