diff --git a/docs/content/_index.md b/docs/content/_index.md index ade04c7c..3f543f48 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -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: diff --git a/docs/content/docs/00-overview.md b/docs/content/docs/00-overview.md deleted file mode 100644 index 89ccb15a..00000000 --- a/docs/content/docs/00-overview.md +++ /dev/null @@ -1,39 +0,0 @@ -# ... - -## 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 and Cats Effecs. -This monadic approach has enabled a wide range of nice features, like composable error handling, cancellation mechanisms and structured concurrency. - -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. - -## Goals - -The goal of this project is to delve into this field through the lens of direct style, developing a few examples (not too complex) leveraging the new strawman library Scala Gears, comparing it with Kotlin Coroutines and the current implementation of monadic Futures, seeking to analyze aspects such as: - -- ergonomics of the two styles (which one result 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. - -## The contribution - -The project is built around three main examples, focusing on different aspects. - -The first is about a simple asynchronous computation that introduces the basics of asynchronous programming using direct style, focusing on the concept of structured concurrency and cancellation. - -The second introduces the basic communication primitive of direct style, channels, and how they can be used to exchange data between concurrent tasks. - -In this context, two contributions are proposed to extend the current Scala Gears library with: - -- terminable channels, i.e. channels that can be terminated, but whose values can still be read by consumers; -- `Flow` Kotlin's Coroutines-like abstraction, modeling a *cold* stream of asynchronously computed values. - -In the last example, it is investigated how to implement a reactive-like event-based system using direct style and, more specifically, the Scala Gears library. -To accomplish this, since the library lacks any kind of reactive transformation and operators, a small set of extension operators (inspired by Rx frameworks) are introduced on top of channels. - -## Conclusions - - -## References - diff --git a/docs/content/docs/01-overview.md b/docs/content/docs/01-overview.md new file mode 100644 index 00000000..8670beb7 --- /dev/null +++ b/docs/content/docs/01-overview.md @@ -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 >}} \ No newline at end of file diff --git a/docs/content/docs/01-boundaries.md b/docs/content/docs/02-boundaries.md similarity index 97% rename from docs/content/docs/01-boundaries.md rename to docs/content/docs/02-boundaries.md index 26e3e204..728e8683 100644 --- a/docs/content/docs/01-boundaries.md +++ b/docs/content/docs/02-boundaries.md @@ -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 >}} diff --git a/docs/content/docs/02-basics.md b/docs/content/docs/03-basics.md similarity index 98% rename from docs/content/docs/02-basics.md rename to docs/content/docs/03-basics.md index be0b8a5c..0fe19a02 100644 --- a/docs/content/docs/02-basics.md +++ b/docs/content/docs/03-basics.md @@ -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 >}} diff --git a/docs/content/docs/03-channels.md b/docs/content/docs/04-channels.md similarity index 97% rename from docs/content/docs/03-channels.md rename to docs/content/docs/04-channels.md index d14b5f8e..fe59c407 100644 --- a/docs/content/docs/03-channels.md +++ b/docs/content/docs/04-channels.md @@ -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 @@ -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: @@ -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: @@ -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 >}} diff --git a/docs/content/docs/05-conclusions.md b/docs/content/docs/05-conclusions.md deleted file mode 100644 index cb15096c..00000000 --- a/docs/content/docs/05-conclusions.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -bookToc: false ---- - -# Conclusions and Final Considerations - -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. - -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. - -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 >}} diff --git a/docs/content/docs/04-rears.md b/docs/content/docs/05-rears.md similarity index 99% rename from docs/content/docs/04-rears.md rename to docs/content/docs/05-rears.md index bd4ab7f4..372fc237 100644 --- a/docs/content/docs/04-rears.md +++ b/docs/content/docs/05-rears.md @@ -558,6 +558,6 @@ suspend fun run(sensorSource: Flow) { - 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 >}}