Extensible effects are an alternative to monad transformers for computing with effects in a functional way. This library uses a “free-er” monad and extensible effects to create an “effect stack” as described in Oleg Kiselyov’s paper.

There are lots of advantages to this approach:

operations can be declared as “requiring” an effect, without the need to fix the full stack of effects in advance

effects handlers are modular and can be replaced with other implementations as needed (even at runtime)

the underlying implementation is performant and stack-safe

existing monadic datatypes can be integrated to the library

effect stacks can be modified or combined

This is probably very abstract so let’s see more precisely what this all means.

A monadic action is modelled as a value of type `Eff[R, A]`

where `R`

denotes a set of effects and `A`

is the value returned by the computation, possibly triggering some effects when evaluated.

The effects `R`

are modelled by a type-level list of “effect constructors”, for example:

```
import cats._, data._
import org.atnos.eff._
type ReaderInt[A] = Reader[Int, A]
type WriterString[A] = Writer[String, A]
type Stack = Fx.fx3[WriterString, ReaderInt, Eval]
```

The stack `Stack`

above declares 3 effects:

a

`ReaderInt`

effect to access some configuration number of type`Int`

a

`WriterString`

effect to log string messagesan

`Eval`

effect to only compute values on demand (a bit like lazy values)

Now we can write a program with those 3 effects, using the primitive operations provided by `ReaderEffect`

, `WriterEffect`

and `EvalEffect`

:

```
import org.atnos.eff.all._
import org.atnos.eff.syntax.all._
// useful type aliases showing that the ReaderInt and the WriterString effects are "members" of R
// note that R could have more effects
type _readerInt[R] = ReaderInt |= R
type _writerString[R] = WriterString |= R
def program[R :_readerInt :_writerString :_eval]: Eff[R, Int] = for {
// get the configuration
n <- ask[R, Int]
// log the current configuration value
_ <- tell("the required power is "+n)
// compute the nth power of 2
a <- delay(math.pow(2, n.toDouble).toInt)
// log the result
_ <- tell("the result is "+a)
} yield a
// run the action with all the interpreters
// each interpreter running one effect
program[Stack].runReader(6).runWriter.runEval.run
```

`> (64,List(the required power is 6, the result is 64))`

As you can see, all the effects of the `Stack`

type are being executed one by one:

- the
`Reader`

effect, which provides a value,`6`

, to each computation needing it - the
`Writer`

effect, which logs messages - the
`Eval`

effect to compute the “power of 2 computation” `run`

extracts the final result

Maybe you noticed that the effects are not being executed in the same order as their order in the stack declaration. The effects can indeed be executed in any order. This doesn’t mean though that the results will be the same. For example running the `Writer`

effect then `Either`

effect returns `String Either (A, List[String])`

whereas running the `Either`

effect then the `Writer`

effect returns `(String Either A, List[String])`

.

This all works thanks to some implicits definitions guiding Scala type inference towards the right return types. You can learn more on implicits in the implicits section.

You can now get a more detailed presentation of the use of the Eff monad by reading the tutorial or you can learn about other effects supported by this library.