Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reduce / fold over list of monoid but reducer returns Either

Ive found myself in the situation a couple of times where i have a reducer / combine fn like so:

  def combiner(a: String, b: String): Either[String, String] = {
    (a + b).asRight[String]
  }

Its a dummy implementation but the fn can fail so it returns an either. I then I have a list of values I want to pass through this with reduce / fold. The best I can come up with (assuming the List's type is a monoid) is this:

  def combine(items: Vector[String]) = {

    items.foldLeft(Monoid[String].empty.asRight[String]) { case (acc, value) =>
      acc.flatMap( accStr => combiner(accStr, value))
    }
  }

Its a bit clumsy and as its a fairly generic pattern I suspect there's a better way to do it using cats.

like image 1000
Luke De Feo Avatar asked Jun 04 '19 22:06

Luke De Feo


1 Answers

You might want to take a look at foldM. Your code would then look approximately like this:

Foldable[Vector].foldM(items, "")(combiner)

The foldM method has the signature

def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) ⇒ G[B])(implicit G: Monad[G]): G[B]

so in your case, the type(-constructor) parameters would unify as follows:

  • G[X] = Either[String, ?]
  • A = String
  • B = String
  • F[X] = Vector[X]

so that f: (A, B) => G[B] would become f: (String, String) => Either[String, String], which is exactly the type of combiner when it's converted into a function.

like image 55
Andrey Tyukin Avatar answered Nov 03 '22 20:11

Andrey Tyukin