How to fold on list of WriterT in cats

222 views Asked by At

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

2

There are 2 answers

1
Nigel Benns On BEST ANSWER

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)))
0
Noam Shaish On

Thanks to @NigelBeans answer. I will just post here the working solutions since I had some implicit conflicts using the exact imports from the answer:

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

val result1 = 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(result1, result2)

val result3 = l.flatSequence

println(result3.run) // (Vector(hello, bye, hi, ciao),Vector(2, 55))