Cookbook

A collection of examples of how to solve particular problems with Eff

Partial Interpretation

It’s common to want to use different effects in different parts of a program. Some effects, like error handling or logging, may extend through the whole of our program. However we may want to include additional effects, like state, within one part.

The example below shows how we can do this

import cats._, data._
      import cats.syntax.all._
      import org.atnos.eff._
      import org.atnos.eff.all._
      import org.atnos.eff.syntax.all._

// Some type definitions for the effects we will use
      type EitherString[A] = Either[String, A]
      type WriterString[A] = Writer[String, A]
      type StateInt[A] = State[Int, A]

      type _err[R] = EitherString |= R
      type _log[R] = WriterString |= R
      type _count[R] = StateInt |= R

      /**
 * In module 1, some utility methods
 */

// repeat a side-effect n times
      def repeatM[M[_]: Monad](n: Int, computation: M[Unit]): M[Unit] =
        if (n <= 0) computation
        else computation >> repeatM(n - 1, computation)

// check a condition and abort computations with a message if the condition is false
      def assert[R: _err](condition: Boolean, msg: String): Eff[R, Unit] =
        if (!condition) left(msg) else right(())

// increment a counter and log the new value
      def incrementCounter[R: _log: _count]: Eff[R, Unit] = for {
        c <- get
        c2 = c + 1
        _ <- tell(s"counter == $c2")
        _ <- put(c2)
      } yield ()

      /**
 * In module 2 your "business" logic
 */

// increment a value n times (n need to be positive)
      def incrementNTimes[R: _err: _log](start: Int, times: Int): Eff[R, Int] = for {
        // this call uses the stack R
        _ <- assert(times >= 0, s"$times is negative")

        // the call uses the stack R plus an additional StateInt effect which is interpreted right away.
        // The other effects remain
        result <- repeatM(times, incrementCounter[Fx.prepend[StateInt, R]]).execState(start)
      } yield result

      /**
 * A top-level call
 */

      type Stack = Fx.fx2[EitherString, WriterString]

      incrementNTimes[Stack](3, 2).runWriter.runEither.run

> Right((6,List(counter == 4, counter == 5, counter == 6)))