scalascala-catswriter-monad

How to fold on list of WriterT in cats


I wish to fold a list of Writer Monad from cats to one big writer without running them. for example:

import cats.data.Writer
import cats.instances.vector._
import cats.instances.list._
import cats.instances.tuple._
import cats.Foldable

val result = for {
  i <- Writer(Vector("hello"), Vector(1))
  j <- Writer(Vector("bye"), Vector(2))
} yield j

val result2 = for {
  x <- Writer(Vector("hi"), Vector(33))
  y <- Writer(Vector("ciao"), Vector(55))
} yield y

val l = List(result, result2)

val result3 = for {
  t <- result
  z <- result2
} yield z // Success !!!

val l = List(result, result2)

// Logically:
// val result3 = for {
//   r <- l
//   o <- r
// } yield o
// But will not compile without Monad Transformer


// With run
val l1: List[(Vector[String], Vector[Int])] = l.map(_.run)

val result5 = Foldable[List].combineAll(l1)

I believe there must be a functional construct for such combination without running the Writers


Solution

  • You can skip right to result5 by using Vector so you have the same container type and then using l.sequence.map(_.flatten) Check out the Traverse typeclass, because as the saying goes "Its always Traverse".

    import cats.data.{Writer, WriterT}
    import cats.instances.vector._
    import cats.instances.list._
    import cats.instances.tuple._
    import cats.{Foldable, Id}
    import cats.implicits._
    
    val result = for {
      i <- Writer(Vector("hello"), Vector(1))
      j <- Writer(Vector("bye"), Vector(2))
    } yield j
    
    val result2 = for {
      x <- Writer(Vector("hi"), Vector(33))
      y <- Writer(Vector("ciao"), Vector(55))
    } yield y
    
    val l = Vector(result, result2)
    
    val result3 = for {
      t <- result
      z <- result2
    } yield z // Success !!!
    
    // val l = List(result, result2) -- this is a duplicate
    
    val result5: WriterT[Id, Vector[String], Vector[Int]] = l.sequence.map(_.flatten)
    

    result5 will have the value:

    WriterT((Vector(hello, bye, hi, ciao),Vector(2, 55)))