2

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?

mdm
  • 3,928
  • 3
  • 27
  • 43
  • 5
    I think that this scenario is not that common (and hence such function isn't already available) because people prefer to work with total functions. If I needed to resort back to monoid's empty in case of undefined function, then I would modify the function itself to include the default case. And in rare cases that I have to use a partial function and I don't have the opportunity to modify it, I would use your approach. Your solution seems a bit convoluted just because it's so general; people would most likely just map their vector in such situation, with `applyOrElse` instead of `apply`. – slouc Jan 12 '18 at 12:08

1 Answers1

2

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.

Michał Płachta
  • 687
  • 6
  • 12