For my examples I will be using Option
s 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?
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>