1

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.

Krzysztof Atłasik
  • 21,985
  • 6
  • 54
  • 76
Matthias Langer
  • 994
  • 8
  • 22

2 Answers2

1

With cats, you can combine multiple options by using mapN. So this would work:

import cats._
import cats.implicits._

val o1 = 1.some
val o2 = 2.some
val o3 = 3.some

(o1,o2,o3).mapN((a,b,c) => a |+| b |+| c) // Some(6)

Olny thing left to do is just unwrapping Tasks and since they're monads, you can do:

for {
  t1 <- task1 
  t2 <- task2 
  t3 <- task3 
} yield (t1,t2,t3)
  .mapN((a,b,c) => /* combine a,b,c */)
Krzysztof Atłasik
  • 21,985
  • 6
  • 54
  • 76
  • Thank you very much for your answer. I was wondering if I could do something like `Applicative[Task].compose[Option].map3(task1, task2, task3)(f)` using `scalaz-zio-interop-cats`, but I couldn't get this into compiling code. – Matthias Langer Mar 16 '19 at 10:32
  • This works indeed - I was originally trying with UIO instead of task. There I get the error `Error:(75, 22) diverging implicit expansion for type cats.kernel.Order[A] ....` – Matthias Langer Mar 16 '19 at 12:18
  • What is this `Onlyc` thing in the middle of the code? And "Olny" is probably a typo. – Andrey Tyukin Mar 16 '19 at 12:39
  • Sorry, I must have accidentally typed it. – Krzysztof Atłasik Mar 16 '19 at 13:16
1

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)
}
Matthias Langer
  • 994
  • 8
  • 22