Creating effects

Creation

New effects can be added to the library pretty easily. Let’s create an Effect for a new “optional” type.

We need:

  import cats._
  import implicits._
  import org.atnos.eff._
  import all._
  import org.atnos.eff.interpret._

  sealed trait Maybe[A]
  case class Just[A](a: A) extends Maybe[A]
  case class Nothing[A]() extends Maybe[A]

  object MaybeEffect {
    type _maybe[R] = Maybe |= R

    def just[R: _maybe, A](a: A): Eff[R, A] =
      send[Maybe, R, A](Just(a))

    def nothing[R: _maybe, A]: Eff[R, A] =
      send[Maybe, R, A](Nothing())

    def runMaybe[R, U, A, B](effect: Eff[R, A])(implicit m: Member.Aux[Maybe, R, U]): Eff[U, Option[A]] =
      recurse(effect)(new Recurser[Maybe, U, A, Option[A]] {
        def onPure(a: A): Option[A] = Some(a)

        def onEffect[X](m: Maybe[X]): X Either Eff[U, Option[A]] =
          m match {
            case Just(x) => Left(x)
            case Nothing() => Right(Eff.pure(None))
          }

        def onApplicative[X, T[_]: Traverse](ms: T[Maybe[X]]): T[X] Either Maybe[T[X]] =
          Right(ms.sequence)
      })

    implicit val applicativeMaybe: Applicative[Maybe] = new Applicative[Maybe] {
      def pure[A](a: A): Maybe[A] = Just(a)

      def ap[A, B](ff: Maybe[A => B])(fa: Maybe[A]): Maybe[B] =
        (fa, ff) match {
          case (Just(a), Just(f)) => Just(f(a))
          case _ => Nothing()
        }
    }
  }

In the code above:

Compiler limitation

When you create an effect you can define a sealed trait and case classes to represent different possibilities for that effect. For example for interacting with a database you might create:

trait DatabaseEffect {

        case class Record(fields: List[String])

        sealed trait Db[A]
        case class Get[A](id: Int) extends Db[Record]
        case class Update[A](id: Int, record: Record) extends Db[Record]
      }

It is recommended to create the Db types outside of the DatabaseEffect trait. Indeed, during Member implicit resolution, depending on how you import the Db effect type (if it is inherited from an object or not) you could experience compiler crashes :-(.

Interpreter

Interpreting a given effect generally means knowing what to do with a value of type M[X] where M is the effect. If the interpreter can “execute” the effect: produce logs (Writer), execute asynchronously (Future), check the value (Either),… then extract a value X, then we can call a continuation to get the next effect and interpret it as well.

The org.atnos.eff.interpret object offers several support traits and functions to write interpreters. In this example we use a Recurser which will be used to “extract” a value X from Maybe[X] or just give up with Eff.pure(None)

The runMaybe method needs an implicit Member.Aux[Maybe, R, U]. This must be read in the following way:


Then we can use this effect in a computation:

import org.atnos.eff._
      import org.atnos.eff.eff._
      import MaybeEffect._

      val action: Eff[Fx.fx1[Maybe], Int] =
        for {
          a <- just(2)
          b <- just(3)
        } yield a + b

      run(runMaybe(action))

> Some(5)