Search code examples
scalascala-cats

How to go from a List[F[String]] to F[List[String]] using Scala and Cats?


I'm new to Cats and I don't know how to overcome this situation. In the code bellow:

class Example[F[_]] {
  import cats._
  import cats.data._
  import cats.syntax.all._

  def saveAll(list: List[String])(implicit M: Monad[F]): F[List[String]] = {
    val result: List[F[String]] =
      list.map(saveOne)
  }

  def saveOne(s: String)(implicit M: Monad[F]): F[String] = s"Saved $s".pure[F]
}

How do I transform the result variable, in the saveAll function, to make sure it matches its expected return type?

Thanks.


Solution

  • This kind of transformation is done with the traverse operation:

    class Example[F[_]] {
      import cats._
      import cats.implicits._
    
      def saveAll(list: List[String])(implicit M: Monad[F]): F[List[String]] = 
        list.traverse(saveOne)
    
      def saveOne(s: String)(implicit M: Monad[F]): F[String] = 
        s"Saved $s".pure[F]
    }
    

    As you can see from the traverse method signature in Traverse typeclass it requires an instance of Applicative, not a Monad:

    trait Traverse[F[_]] {
      def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
    }
    

    In cats every type that has a Monad also has an Applicative, so the Example class works even with Monad.

    But the reverse is not true. Some types only have an Applicative instance. The most notable of those is Validated. You can read more about the problem with implementing Monad for Validated in the cats documentation.

    So this code will be more general if you request an instance of Applicative instead:

    class Example[F[_]] {
      import cats._
      import cats.implicits._
    
      def saveAll(list: List[String])(implicit M: Applicative[F]): F[List[String]] = 
        list.traverse(saveOne)
    
      def saveOne(s: String)(implicit M: Applicative[F]): F[String] = 
        s"Saved $s".pure[F]
    }