scalascalazscala-catsscalaz7zio

How to elegantly combine multiple Tasks containing options in ZIO


I'm looking for the most elegant implementation of

import scalaz.zio.Task

def combineTasks[A, B, C, D](task1: Task[Option[A]],
                             task2: Task[Option[B]],
                             task3: Task[Option[C]])
                            (f: (A, B, C) => D)
: Task[Option[D]]

using

  1. no additional dependencies
  2. scalaz-zio-interop-cats and cats
  3. scalaz-zio-interop-scalaz7x and scalaz7x

The solutions should generalize well to n arguments.


Solution

  • After getting some help and doing research, I found the following implementations, that seem the most elegant to me so far:

    1. Using no additional dependencies:

    def combineTasks[A, B, C, D](task1: Task[Option[A]],
                                 task2: Task[Option[B]],
                                 task3: Task[Option[C]])
                                (f: (A, B, C) => D)
    : Task[Option[D]] = {
      for {
        t1 <- task1
        t2 <- task2
        t3 <- task3
      } yield {
        (t1, t2, t3) match {
          case (Some(t1), Some(t2), Some(t3)) => Some(f(t1, t2, t3))
          case _ => None
        }
      }
    }
    

    2. Using scalaz-zio-interop-cats and cats:

    def combineTasks[A, B, C, D](task1: Task[Option[A]],
                                 task2: Task[Option[B]],
                                 task3: Task[Option[C]])
                                (f: (A, B, C) => D)
    : Task[Option[D]] = {
      import cats.implicits.catsStdInstancesForOption
      import cats.Apply
      import scalaz.zio.interop.catz._
    
      Apply[Task].compose[Option].map3(task1, task2, task3)(f)
    }
    

    See mapN over composed Apply for a related discussion.

    3. Using scalaz-zio-interop-scalaz7x and scalaz7x:

    def combineTasks[A, B, C, D](task1: Task[Option[A]],
                                 task2: Task[Option[B]],
                                 task3: Task[Option[C]])
                                (f: (A, B, C) => D): Task[Option[D]] = {
      import scalaz.Apply
      import scalaz.std.option._
      import scalaz.zio.interop.scalaz72._
    
      Apply[Task].compose[Option].apply3(task1, task2, task3)(f)
    }