Search code examples
scalascala-cats

mapN over composed Apply


I know that I can compose Apply to work with nested structures, like in

def mapAll[A, B, C, D](o1: List[Option[A]],
                       o2: List[Option[B]],
                       o3: List[Option[C]])
                      (f: (A, B, C) => D)
: List[Option[D]] = {
  import cats._
  import cats.implicits._
  Apply[List].compose[Option].map3(o1, o2, o3)(f)
}

However, is there a way to convince the compiler to accept (o1, o2, o3).mapN(f) instead of Apply[List].compose[Option].map3(o1, o2, o3)(f), so that mapN gets applied using the composed Apply?


Solution

  • This is exactly what cats.data.Nested is for:

    def mapAll[A, B, C, D](o1: List[Option[A]],
                           o2: List[Option[B]],
                           o3: List[Option[C]])
                          (f: (A, B, C) => D)
    : List[Option[D]] = {
      import cats.data.Nested
      import cats.instances.list._, cats.instances.option._
      import cats.syntax.apply._
    
      (Nested(o1), Nested(o2), Nested(o3)).mapN(f).value
    }
    

    (Note that you will need to enable the -Ypartial-unification compiler flag for the code above to compile. Alternatively you could add some type parameters, but off the top of my head I'm not sure exactly where they'd be required, and if you're doing anything with Cats -Ypartial-unification is pretty much necessary, anyway.)

    You could also just make the composed instance available implicitly:

    import cats.Apply
    import cats.instances.list._, cats.instances.option._
    import cats.syntax.apply._
    
    type ListOption[x] = List[Option[x]]
    
    implicit val listOptionApply: Apply[ListOption] = Apply[List].compose[Option]
    
    def mapAll[A, B, C, D](o1: ListOption[A],
                           o2: ListOption[B],
                           o3: ListOption[C])
                          (f: (A, B, C) => D)
    : List[Option[D]] = (o1, o2, o3).mapN(f)
    

    This really isn't ideal, though—it's non-standard and extremely fragile (e.g. the type aliases are necessary to guide resolution).

    In my view your best bet is just to write out Apply[List].compose[Option] explicitly and skip the fancy tuple syntax, but if you really have to have the fancy tuple syntax, go with Nested.