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:

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

First example

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:

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

> (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:

  1. the Reader effect, which provides a value, 6, to each computation needing it
  2. the Writer effect, which logs messages
  3. the Eval effect to compute the “power of 2 computation”
  4. 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.