Search code examples
scalamonadsimplicitscala-catsfor-comprehension

Implicits resolution with Scala-cats


Example is taken from Tagless Final in Mastering Functional Programming:

trait Capabilities[F[_]] {
  def resource(name: String): F[String]
  def notify(target: String, text: String): F[Unit]
}

import cats.Monad

def income[F[_]](implicit M: Monad[F], C: Capabilities[F]): F[Unit] =
  for {
    contents <- C.resource("sales.csv")
    total = contents
      .split("\n").toList.tail  // Collection of lines, drop the CSV header
      .map { _.split(",").toList match  // List[Double] - prices of each of the entries
    { case name :: price :: Nil => price.toDouble }
    }
      .sum
    _ <- C.notify("admin@shop.com", s"Total income made today: $total")
  } yield ()

In order to compile is, I must include:

import cats.implicits._

Without this, I get an error:

Error:(21, 27) value map is not a member of type parameter F[String] contents <- C.resource("sales.csv")

Two questions:

  1. Type of F is not known in advance. But there are Monad and Capabilities define implicitly for F. Why Scala compiler cannot identify it without implicits import from the cats.
  2. Usually I prefer to find a certain type and do not import all stuff from the cats. For instance, import only cats.instances.list._, to be more precise. What exactly is used by Scala compiler from cats.implicits._, in order to be able to compile this code? And more important, what algorithm you use, to find it?
  3. I've also found, if I add -Xprint-args option, code is compiled successfully even without cats.implicits._ importing. Can you please explain, how it affects it?

Solution

    1. What Scala compiler cannot identify without implicits import are just syntaxes. Instances are resolved properly without imports (because of implicit parameters).

    2. Out of cats.implicits._ you actually use only

      import cats.syntax.functor._
      import cats.syntax.flatMap._
      
    3. https://github.com/scala/scala/pull/5909

      The option behaves like -Xshow-phases and stops the compiler after printing.


    Without syntaxes imported you can write

    def income[F[_]](implicit M: Monad[F], C: Capabilities[F]): F[Unit] =
      M.flatMap(C.resource("sales.csv"))(contents => {
        val total = contents
          .split("\n").toList.tail // Collection of lines, drop the CSV header
          .map {
            _.split(",").toList match // List[Double] - prices of each of the entries
              { case name :: price :: Nil => price.toDouble }
          }
          .sum
        M.map(C.notify("admin@shop.com", s"Total income made today: $total"))(_ => ())
      })