There is a better way:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scalaz._, Scalaz._
def k[A, B, C](a: A): Kleisli[Future, B, Option[C]] = ???
def ks[A, B, C](as: List[A]): Kleisli[Future, B, List[C]] =
as.traverseU(k[A, B, C]).map(_.flatten)
Note that I'm using List
instead of Seq
since Scalaz doesn't provide a Traverse
instance for Seq
(see my answer here for some discussion of why it doesn't).
This is one of the big advantages of using Kleisli
in the first place—if F
has an Applicative
instance, then so does Kleisli[F, In, ?]
for any In
. In this case that means that you can use traverse
instead of manually sequencing with map
and Future.sequence
.
Update: want to get super-fancy here? Probably not, but just in case, you could actually take the abstraction one last step and move the context of the return type into the context of the Kleisli
:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scalaz._, Scalaz._
type FutureOption[x] = OptionT[Future, x]
type FutureList[x] = ListT[Future, x]
def k[A, B, C](a: A): Kleisli[FutureOption, B, C] = ???
def ks[A, B, C](as: List[A]): Kleisli[FutureList, B, C] =
Kleisli.kleisliMonadTrans[B].liftMU(
ListT.fromList(as.point[Future])
).flatMap(k[A, B, C](_).mapK[FutureList, C](_.toListT))
This allows us to map, etc. directly over the result type while ignoring the fact that we're getting that result in a list in a future after applying the Kleisli
to an input value.
Concretely:
import scala.util.Try
def k(s: String): Kleisli[FutureOption, Int, Int] = Kleisli[FutureOption, Int, Int] { in =>
OptionT(Future(Try(s.toInt + in).toOption))
}
def ks(as: List[String]): Kleisli[FutureList, Int, Int] =
Kleisli.kleisliMonadTrans[Int].liftMU(
ListT.fromList(as.point[Future])
).flatMap(k(_).mapK[FutureList, Int](_.toListT))
And then:
import scala.concurrent.Await
import scala.concurrent.duration._
scala> import scala.concurrent.Await
import scala.concurrent.Await
scala> import scala.concurrent.duration._
import scala.concurrent.duration._
scala> Await.result(ks(List("1", "2", "a", "3")).run(0).run, 1.second)
res0: List[Int] = List(1, 2, 3)
And the punchline:
scala> val mapped = ks(List("1", "2", "a", "3")).map(_ + 1)
mapped: scalaz.Kleisli[FutureList,Int,Int] = Kleisli(<function1>)
scala> Await.result(mapped.run(0).run, 1.second)
res1: List[Int] = List(2, 3, 4)
Should you actually do this? Again, probably not, but it works, and it is kind of cool to be able to map way into a complex computation like this.