Search code examples
scalafunctional-programmingscalaz

Defining Monad, Applicative, and Functor instances for a type class


Let's say I have defined a type class for Caching computations.

trait Cached[F[_], A] {
  def value: F[A]
}

Intuitively, Cached wraps the computation so we can either evaluate it at run time or load the result from the database instead.

I would like to define Functor, Applicative, and Monad instances for this trait. Using Kind-projector to make my life easier:

import scalaz._, Scalaz._
object Cached {

  def apply[F[_], A](f: => F[A]): Cached[F, A] = new Cached[F, A] {
    override def value: F[A] = f
  }

  implicit def functor[F[_] : Functor]: Functor[Cached[F, ?]] = new Functor[Cached[F, ?]] {
    override def map[A, B](fa: Cached[F, A])(f: A => B): Cached[F, B] =
      Cached(fa.value map f)
  }

  implicit def applicative[F[_] : Applicative]: Applicative[Cached[F, ?]] = new Applicative[Cached[F, ?]] {
    override def point[A](a: => A): Cached[F, A] = Cached(a.point[F])

    override def ap[A, B](fa: => Cached[F, A])(f: => Cached[F, A => B]): Cached[F, B] =
      Cached(fa.value <*> f.value)
  }

  implicit def monad[F[_] : Monad](implicit app: Applicative[Cached[F, ?]], func: Functor[Cached[F, ?]]): Monad[Cached[F, ?]] =
    new Monad[Cached[F, ?]] {
      override def point[A](a: => A): Cached[F, A] = app.point(a)

      override def bind[A, B](fa: Cached[F, A])(f: A => Cached[F, B]): Cached[F, B] =
        Cached(func.map(fa)(f).value >>= (_.value))
    }
}

So far, so good. Now, let's use the monad in a simple example:

import Cached._
val y = Cached(2.point[Id])
val z = for {
  a <- Cached(1.point[Id])
  b <- y
} yield a + b

Running the code, I get the following error at run time:

[error] diverging implicit expansion for type scalaz.Applicative[[β$4$]Cached[scalaz.Scalaz.Id,β$4$]]
[error] starting with method monad in object Cached
[error]       a <- Cached(1.point[Id])
[error]                  ^
[error] diverging implicit expansion for type scalaz.Applicative[[β$4$]Cached[scalaz.Scalaz.Id,β$4$]]
[error] starting with method monad in object Cached
[error]       b <- y
[error]            ^
[error] two errors found
[error] (Test / compileIncremental) Compilation failed

I know diverging implicit expansion happens when the compiler stuck in a loop when expanding the implicit definitions, but I can't see why that is the case with my code.

I would appreciate if someone could point me to the right direction. I am pretty new to functional programming concepts so what I have done here might not even make sense!


Solution

  • I ended up defining the instances like this:

    implicit def instance[F[_] : Monad]: Functor[Cached[F, ?]] with Applicative[Cached[F, ?]] with Monad[Cached[F, ?]] =
        new Functor[Cached[F, ?]] with Applicative[Cached[F, ?]] with Monad[Cached[F, ?]] {
          def eval[A](fa: => Cached[F, A]): F[A] = {
            println("loading stuff from the database...")
            fa.value
          }
    
          override def point[A](a: => A): Cached[F, A] =
            Cached(a.point[F])
    
          override def map[A, B](fa: Cached[F, A])(f: A => B): Cached[F, B] = {
            Cached(eval(fa) map f)
          }
    
          override def bind[A, B](fa: Cached[F, A])(f: A => Cached[F, B]): Cached[F, B] = {
            Cached(eval(fa) >>= (a => f(a).value))
          }
    
          override def ap[A, B](fa: => Cached[F, A])(f: => Cached[F, A => B]): Cached[F, B] =
            Cached(eval(fa) <*> f.value)
        }