Applicative provides ap
and pure
, but does not guarantee to provide flatMap
, which is provided by Monad:
Monad
extends the Applicative
type class with a new function flatten
.
If F
was a monad, then at least in scalaz we might use ListT
, for example,
import scalaz._
import ListT._
import scalaz.std.option._
val animals: Option[List[String]] = Some(List("Dog", "Cat", "Bird"))
def getBreeds(animal: String): Option[List[String]] = ???
(for {
animal <- listT(animals)
breed <- listT(getBreeds(animal))
} yield breed).run
However cats does not seem to provide ListT:
A naive implementation of ListT
suffers from associativity issues; ... It’s possible to create a ListT
that doesn’t
have these issues, but it tends to be pretty inefficient. For many
use-cases, Nested
can be used to achieve the desired results.
Here is an attempt at a mad solution that you should not use. Consider Validated
which only has Applicative
instance. Let us provide a Monad
instance even though Validated is not a Monad:
implicit def validatedMonad[E]: Monad[Validated[E, *]] =
new Monad[Validated[E, *]] {
def flatMap[A, B](fa: Validated[E, A])(f: A => Validated[E, B]): Validated[E, B] =
fa match {
case Valid(a) => f(a)
case i @ Invalid(_) => i
}
def pure[A](x: A): Validated[E, A] = Valid(x)
def tailRecM[A, B](a: A)(f: A => Validated[E, Either[A, B]]) = ???
}
The implementation of validatedMonad
is taken from scala-exercises.org/cats/validated.
Next let us make scalaz's listT
available within cats via shims interop layer
libraryDependencies += "com.codecommit" %% "shims" % "2.1.0"
Putting it all together, we have
import cats._
import cats.Monad
import cats.data.Validated.{Invalid, Valid}
import cats.data.{Nested, OptionT, Validated, ValidatedNec}
import cats.implicits._
import scalaz.ListT._
import shims._
implicit def validatedMonad[E]: Monad[Validated[E, *]] =
new Monad[Validated[E, *]] {
def flatMap[A, B](fa: Validated[E, A])(f: A => Validated[E, B]): Validated[E, B] =
fa match {
case Valid(a) => f(a)
case i @ Invalid(_) => i
}
def pure[A](x: A): Validated[E, A] = Valid(x)
def tailRecM[A, B](a: A)(f: A => Validated[E, Either[A, B]]) = ???
}
val animals: Validated[String, List[String]] = List("Dog", "Cat", "Bird").valid
def getBreeds(animal: String): Validated[String, List[String]] = ???
(for {
animal <- listT(animals)
breed <- listT(getBreeds(animal))
} yield breed).run
Note this "solution" breaks monadic laws, is not general, and is likely to cause confusion, so do not use it.
Applicative
is that it doesn't have the ability toflatMap
. - Jasper-M