https://github.com/rcardin/yaes
An experimental effect system in Scala mixing monadic and direct-style
https://github.com/rcardin/yaes
algebraic-effects direct-style effect-system monad scala3
Last synced: 4 months ago
JSON representation
An experimental effect system in Scala mixing monadic and direct-style
- Host: GitHub
- URL: https://github.com/rcardin/yaes
- Owner: rcardin
- License: mit
- Created: 2024-11-18T07:57:42.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-02-21T16:23:19.000Z (4 months ago)
- Last Synced: 2025-02-21T17:27:17.465Z (4 months ago)
- Topics: algebraic-effects, direct-style, effect-system, monad, scala3
- Language: Scala
- Homepage:
- Size: 85 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README

# Yet Another Effect System (yaes)
An experimental effect system in Scala mixing monadic and direct-style
## Introduction to Algebraic Effects
Algebraic effects are a way to represent and handle side effects in a program in a modular and composable manner. Unlike traditional approaches such as über-monads, algebraic effects separate the definition of effects from their implementation. This allows for more flexible and reusable code.
In essence, an algebraic effect system consists of:
1. **Effect Signatures**: These define the operations that can be performed.
2. **Effect Handlers**: These provide the implementation for the operations.By using algebraic effects, you can write code that is easier to reason about, test, and maintain. The effects are described declaratively, and their execution is deferred until they are handled.
## Effects
### Async
The `Async` effect allows for asynchronous computations and fiber management.
Example:
```scala 3
import in.rcard.yaes.Async
import scala.concurrent.duration.*val result = Async.run {
val fiber = Async.fork {
Async.delay(1.second)
42
}
fiber.value
}println(result) // Output: 42
```### Input
The Input effect allows for reading input from the console.
Example:
```scala 3
import in.rcard.yaes.{Input, Raise}
import java.io.IOExceptionval result = Raise.run {
Input.run {
Input.readLn()
}
}println(result) // Output: (depends on user input)
```### IO
The IO effect allows for running side-effecting operations.
Example:
```scala 3
import in.rcard.yaes.IOval result = IO.run {
val fortyTwo: IO ?=> Int = IO {
42
}
fortyTwo + 1
}println(result) // Output: 43
```### Output
The Output effect allows for printing output to the console.
Example:
```scala 3
import in.rcard.yaes.OutputOutput.run {
Output.printLn("Hello, World!")
}
```### Random
The Random effect allows for generating random numbers.
Example:
```scala 3
import in.rcard.yaes.Randomval result = Random.run {
Random.nextInt
}println(result) // Output: (random integer)
```## References
It follows some quotations and links to valuable resources to understand the concepts behind the library:
1. [Introduction to Abilities: A Mental Model - What do we mean by effects](https://www.unison-lang.org/docs/fundamentals/abilities/#what-do-we-mean-by-effects):
> […] You might think of an effectful computation as one which performs an action outside of its local scope compared to one which simply returns a calculable value. […] So when functional programmers talk about managing effects, they're talking about expressing the basic logic of their programs within some guard rails provided by data structures or programming language constructs.2. [Abilities, not monads](https://softwaremill.com/trying-out-unison-part-3-effects-through-abilities/)
> […] Unison offers abilities, which are an implementation of algebraic effects. An ability is a property of a function (it's not part of the value's type!).
3. [Abilities for the monadically inclined](https://www.unison-lang.org/docs/fundamentals/abilities/for-monadically-inclined/)4. [Effect Oriented Programming, by Bill Frasure, Bruce Eckel, James Ward](https://effectorientedprogramming.com/)
> An Effect is an unpredictable interaction, usually with an external system. […] An Effect System manages Effects by wrapping these calls. […] Unpredictable elements are Side Effects. […] A Side Effect occurs when calling a function changes the context of that function. […] There’s an important difference: Side Effects are unmanaged and Effects are managed. A Side Effect “just happens” but an Effect is explicitly tracked and controlled. […] With an Effect System, we manage Effect behavior by putting that Effect in a kind of box. […] An Effect System provides a set of components that replace Side-Effecting functions in standard libraries, along with the structure for managing Effectful functions that you write. An Effect System enables us to add almost any functionality to a program. […] Managing an Effect means we not only control what results are produced by a function like `nextInt()`, but also when those results are produced. The control of when is called deferred execution. Deferred execution is part of the solution for easily attaching functionality to an existing program. […] Deferring the execution of an Effect is part of what enables us to add functionality to that Effect. […] If Effects ran immediately, we could not freely add behaviors. […] When we manage an Effect, we hold a value that represents something that can be run but hasn’t yet.
5. [An Introduction to Algebraic Effects and Handlers](https://www.eff-lang.org/handlers-tutorial.pdf)
> The idea behind it is that operation calls do not perform actual effects (e.g. printing to an output device), but behave as signals that propagate outwards until they reach a handler with a matching clause6. [CanThrow Capabilities](https://docs.scala-lang.org/scala3/reference/experimental/canthrow.html)
7. [Essential Effects, by Adam Rosien](https://essentialeffects.dev/)
> we’ll distinguish two aspects of code: computing values and interacting with the environment. At the same time, we’ll talk about how transparent, or not, our code can be in describing these aspects. […] To understand what plusOne does, you don’t have to look anywhere except the (literal) definition of plusOne. There are no references to anything outside of it. This is sometimes referred to as local reasoning. Under substitution, programs mean the same thing if they evaluate to the same value. 13 + 1 means exactly the same thing as 14. So does plusOne(12 + 1), or even (12 + 1) + 1. This is known as referential transparency. […] If we impose some conditions, we can tame the side effects into something safer; we’ll call these effects. […] The type of the program should tell us what kind of effects the program will perform, in addition to the type of the value it will produce. If the behavior we want relies upon some externally-visible side effect, we separate describing the effects we want to happen from actually making them happen. We can freely substitute the description of effects until the point we run them. […] We delay the side effect so it executes outside of any evaluation, ensuring substitution still holds within. We’ll call these conditions the Effect Pattern. […] We can construct individual effects, and run them, but how do we combine them? We may want to modify the output of an effect (via map), or use the output of an effect to create a new effect (via flatMap). But be careful! Composing effects must not execute them.8. [Koka Language - 3.4. Effect Handlers](https://koka-lang.github.io/koka/doc/book.html#sec-handlers)
> Effect handlers are a novel way to define control-flow abstractions and dynamic binding as user defined handlers – no need anymore to add special compiler extensions for exceptions, iterators, async-await, probabilistic programming, etc. Moreover, these handlers can be composed freely so the interaction between, say, async-await and exceptions are well-defined.9. [Algebraic Effects from Scratch by Kit Langton](https://www.youtube.com/watch?v=qPvPdRbTF-E&t=763s)