Search code examples
scalaimplicitscala-cats

Importing FlatMap instance breaks Applicative builder syntax


For my examples I will be using Options but in my actual code I was using a custom datatype. Adding the import import cats.std.option._ will resolve the issue in the examples. I had some code that looked like this:

import cats.{FlatMap, Eval, Applicative}
import cats.data.Kleisli
import cats.syntax.all._

implicit val optionApplicative = new Applicative[Option] {
  override def pure[A](x: A): Option[A] = Option(x)
  override def ap[A, B](fa: Option[A])(f: Option[(A) => B]): Option[B] = for {
    a <- fa
    fUnwrapped <- f
  } yield fUnwrapped(a)
}

val test : Option[(Int, String)] = (Option(4) |@| Option("name")).map((_, _))

This code was compiling and running just fine.

Next I used Kleisli to compose some functions that return Option:

val test2 : List[Int] => Option[Int] = {
  val f = (xs : List[Int]) => xs.headOption
  val g = (x : Int) => Option(x)
  Kleisli(f).andThen(g).run
}

The code did not compile because my data type did not have a FlatMap instance. I created one:

implicit val optionFlatmap = new FlatMap[Option] {
  override def flatMap[A, B](fa: Option[A])(f: (A) => Option[B]): Option[B] = fa.flatMap(f)
  override def map[A, B](fa: Option[A])(f: (A) => B): Option[B] = fa.map(f)
}

After importing the FlatMap instance (or defining it in the same file), the builder syntax no longer compiles. I get the error:

error: value |@| is not a member of Option[Int]
[ERROR]   val test : Option[(Int, String)] = (Option(4) |@| Option("name")).map((_, _))
[ERROR]                                                 ^

Why does importing the FlatMap instance break the builder syntax? How can I fix this?


Solution

  • It is helpful to debug this using the -Xlog-implicits compiler flag, which will tell you which implicits the compiler tried, and why they failed. In your case, the very first message (at least for me) explains it:

    scala> (Option(4) |@| Option("name")).map((_, _))
    <console>:20: applySyntax is not a valid implicit value for Option[Int] => ?{def |@|: ?} because:
    ambiguous implicit values:
     both value optionApplicative of type => cats.Applicative[Option]
     and value optionFlatmap of type => cats.FlatMap[Option]
     match expected type cats.Apply[Option]
           (Option(4) |@| Option("name")).map((_, _))
    

    For the apply operations to work, an implicit Apply[Option] is needed, but both Applicative and FlatMap extend Apply, so the compiler does not know which one to choose. The simple solution to this is to combine both instances into an instance of Monad, which extends both Applicative and FlatMap.

    import cats._
    import cats.data.Kleisli
    import cats.syntax.all._
    
    implicit val optionMonad = new Monad[Option] {
    
      def pure[A](x: A): Option[A] = Option(x)
    
      def flatMap[A, B](fa: Option[A])(f: (A) => Option[B]): Option[B] = fa.flatMap(f)
    
    }
    

    Now everything works:

    scala> val test : Option[(Int, String)] = (Option(4) |@| Option("name")).map((_, _))
    test: Option[(Int, String)] = Some((4,name))
    
    scala> val test2 : List[Int] => Option[Int] = {
      val f = (xs : List[Int]) => xs.headOption
      val g = (x : Int) => Option(x)
      Kleisli(f).andThen(g).run
    }
    
    test2: List[Int] => Option[Int] = <function1>