michallos
michallos

Reputation: 55

How to flatMap cats Applicatives

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

Answers (2)

Dmytro Mitin
Dmytro Mitin

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

Mario Galic
Mario Galic

Reputation: 48420

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.

Upvotes: 2

Related Questions