Foldable "foldMap" that take a partial function: foldCollect?

399 Views Asked by At

Say, I have the following object:

case class MyFancyObject(a: String, b: Int, c : Vector[String])

And what I needed is to get a single Vector[String] containing all 'c's that match a given partial function.

E.g.:

val xs = Vector(
  MyFancyObject("test1",1,Vector("test1-1","test1-2","test1-3")),
  MyFancyObject("test2",2,Vector("test2-1","test2-2","test2-3")),
  MyFancyObject("test3",3,Vector("test3-1","test3-2","test3-3")),
  MyFancyObject("test4",4,Vector("test4-1","test4-2","test4-3"))
)

val partialFunction1 : PartialFunction[MyFancyObject,Vector[String]] = {
  case MyFancyObject(_,b,c) if b > 2 => c
}

What I need to get is: Vector("test3-1","test3-2","test3-3","test4-1","test4-2","test4-3").

I solved this doing the following:

val res1 = xs.foldMap{
  case MyFancyObject(_,b,c) if b > 2 => c
  case _ => Vector.empty[String]
}

However, this made me curious. What I am doing here seemed to be a pretty common and natural thing: for each element of a foldable collection, try to apply a partial function and, should that fail, default to the Monoid's empty (Vector.empty in my case). I searched in the library and I did not find anything doing this already, so I ended up adding this extension method in my code:

  implicit class FoldableExt[F[_], A](foldable : F[A]) {
    def foldCollect[B](pF: PartialFunction[A, B])(implicit F : Foldable[F], B : Monoid[B]) : B = {
      F.foldMap(foldable)(pF.applyOrElse(_, (_ : A) => B.empty))
    }
  }

My question here is:

Is there any reason why such a method would not be in available already? Is it not a generic and common enough scenario, or am I missing something?

1

There are 1 best solutions below

0
On

I think that if you really need the partial function, you don't want it to leak outside, because it's not very nice to use. The best thing to do, if you want to reuse your partialFunction1, is to lift it to make it a total function that returns Option. Then you can provide your default case in the same place you use your partial function. Here's the approach:

val res2 = xs.foldMap(partialFunction1.lift).getOrElse(Vector.empty)

The foldMap(partialFunction1.lift) returns Some(Vector(test3-1, test3-2, test3-3, test4-1, test4-2, test4-3)). This is exactly what you have in res1, but wrapped in Option.