Search code examples
scalacompilationfuturefunctorscala-cats

Why adding import `import cats.instances.future._` will result an compilation error for implicit Functor[Future]


The scala code is using cats and works well:

import cats.implicits._
import cats.Functor
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

object Hello extends App {

  Functor[Future].map(Future("hello"))(_ + "!")

}

But if I add this import:

import cats.instances.future._

It will report such compilation errors:

 Error:(18, 10) could not find implicit value for parameter instance: cats.Functor[scala.concurrent.Future]
   Functor[Future].map(Future("hello"))(_ + "!")

Why it happens, and how can I debug it to find reason? I used all kinds of ways I know, but can't find anything.

The build.sbt file is:

name := "Cats Implicit Functor of Future Compliation Error Demo"

version := "0.1"

organization := "org.my"

scalaVersion := "2.12.4"

sbtVersion := "1.0.4"

libraryDependencies ++= Seq(
  "org.typelevel" %% "cats-core" % "1.0.1"
)

Solution

  • The object cats.implicits has the FutureInstances trait as a linear supertype. The FutureInstances has an implicit catsStdInstancesForFuture method, which produces a Monad[Future], which in turn is a Functor[Future].

    On the other hand, the object cats.instances.future also mixes in FutureInstances, so it again provides an implicit method catsStdInstancesForFuture, but through another pathway.

    Now the compiler has two possibilities to generate a Functor[Future]:

    • by invoking cats.instances.future.catsStdInstancesForFuture
    • by invoking cats.implicits.catsStdInstancesForFuture

    Since it cannot decide which one to take, it exits with an error message.

    To avoid that, don't use cats.implicits._ together with cats.instances.future._. Either omit one of the imports, or use the

    `import packagename.objectname.{member1name, member2name}`
    

    to select only those implicits that you need.


    Adding "-print" to scalacOptions could help when debugging implicits:

    scalacOptions ++= Seq(
      ...
      "-print",
      ...
    )
    

    It will print out the desugared code with cats.implicits. and cats.instances.-pieces added everywhere. Unfortunately, it tends to produce quite a lot of noise.


    The more fundamental reason why this happens is that there is no way to define higher-dimensional-cells (kind-of "homotopies") between the two (equivalent) pathways that lead to a Functor[Future]. If we had a possibility to tell the compiler that it doesn't matter which path to take, then everything would be much nicer. Since we can't do it, we have to make sure that there is always only one way to generate an implicit Functor[Future].