Is there an easy way to convert a tuple of type (Future[A], Future[B], Future[C], ..., Future[N]) to Future[(A, B, C, ..., N)]? This assumes an undefined number of elements in the tuple.
I've tried converting the tuple to HList and tried a similar trick of foldLeft and for comprehension as done in Future.sequence but no luck dealing with the types passed into the fold. Tried the same with recursive functions. But this code still does not compile due to missing HList.head, HList.tail. The code looks like follows:
def sequence[L <: HList](in: L)(implicit ihc: IsHCons[L]) = {
val list = for (i <- in.head; j <- in.tail.head) yield HList(i, j)
@tailrec
def sequence2(fList: Future[HList], listF: HList): Future[HList] = {
if (listF == HNil) fList
else {
val nextFList = for (l <- fList; e <- listF.head.asInstanceOf[Future[_]]) yield l :+ e
sequence2(nextFList, listF.tail)
}
}
sequence2(list, in.tail.tail)
}
This code should return Future[HList] which we can then map back to tuple with the tupled function. Ideally we need to check for the fact we have less than 3 elements in the tuple. But lets assume the input is an HList of size 3 or larger for this exercise.
I'm on Shapeless 1.2.4 and can't move yet due to other dependencies.
Thanks in advance!
Don't know whether this counts as "easy", but Dan Lien and I were discussing how to sequence tuples of Option
s just the other day, and my solution for that case can be straightforwardly adapted to work for Future
(note that I'm using scalaz-contrib's monad instance for Future
; if you're on Scalaz 7.1 this isn't necessary):
import scala.concurrent.{ ExecutionContext, Future }
import scalaz._, Scalaz._, contrib.std.scalaFuture._
import shapeless._, ops.hlist.{ RightFolder, Tupler }
// Might as well stay generic in `F` for this part.
object applicativeFolder extends Poly2 {
implicit def caseApplicative[A, B <: HList, F[_]](implicit
app: Applicative[F]
) = at[F[A], F[B]] {
(a, b) => app.ap(a)(app.map(b)(bb => (_: A) :: bb))
}
}
// It should be possible to make this part generic in `F` as well,
// but type inference makes it tricky, so we specialize to `Future`.
def sequence[T, EL <: HList, L <: HList, OL <: HList, OT](t: T)(implicit
executor: ExecutionContext,
gen: Generic.Aux[T, EL],
eq: EL =:= L,
folder: RightFolder.Aux[L, Future[HNil], applicativeFolder.type, Future[OL]],
tupler: Tupler.Aux[OL, OT]
): Future[OT] =
eq(gen.to(t)).foldRight(Future.successful(HNil: HNil))(applicativeFolder).map(
tupler(_)
)
Oh, just noticed that you're on 1.2.4. The necessary changes are essentially mechanical—something like the following should work:
// It should be possible to make this part generic in `F` as well,
// but type inference makes it tricky, so we specialize to `Future`.
def sequence[T, L <: HList, OL <: HList, OT](t: T)(implicit
executor: ExecutionContext,
hlister: HListerAux[T, L],
folder: RightFolderAux[L, Future[HNil], applicativeFolder.type, Future[OL]],
tupler: TuplerAux[OL, OT]
): Future[OT] =
t.hlisted.foldRight(Future.successful(HNil: HNil))(applicativeFolder).map(
tupler(_)
)
It works like this:
scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global
scala> val result = sequence((Future(1), Future('a)))
result: scala.concurrent.Future[(Int, Symbol)] = ...
scala> result.foreach(println)
(1,'a)
Note that there is a sequence
implementation in shapeless-contrib, but for various reasons (involving type inference) it's difficult to use in this situation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With