Reputation: 55
I've started learning functional programming with Cats and I stuck with flatMapping (merging) applicatives F[List]
.
What is very simple in pure Scala is flatmapping list of lists like that:
val animals = List("Dog", "Cat", "Bird")
def getBreads(animal: String): List[String] = ...
val allAnimalsBreads = animals.flatMap(animal => getBread(animal)) // this will be just List[String]
How can I do the same if everything is wrapped with applicative?:
val animals = List("Dog", "Cat", "Bird").pure[F]
def getBreads(animal: String): F[List[String]] = ...
val allAnimalsBreads = ? // this should be F[List[String]]
Upvotes: 3
Views: 1864
Reputation: 51658
For Applicative
this is impossible. For Monad
if you don't want to use unconventional transformer ListT
you can do
import cats.syntax.traverse._
import cats.syntax.applicative._
import cats.syntax.functor._
import cats.syntax.flatMap._
import cats.instances.list._
val allAnimalsBreads: F[List[String]] =
animals.map(_.map(getBreads)) // F[List[F[List[String]]]]
.map(_.sequence) // F[F[List[List[String]]]]
.flatten // F[List[List[String]]]
.map(_.flatten) // F[List[String]]
Upvotes: 2
Reputation: 48420
Applicative provides ap
and pure
, but does not guarantee to provide flatMap
, which is provided by Monad:
Monad
extends theApplicative
type class with a new functionflatten
.
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 aListT
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.
Upvotes: 2