Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using shapeless to convert tuple of Future to Future of tuple by way of HList

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!

like image 216
akara Avatar asked May 04 '14 07:05

akara


1 Answers

Don't know whether this counts as "easy", but Dan Lien and I were discussing how to sequence tuples of Options 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.

like image 194
Travis Brown Avatar answered Oct 14 '22 03:10

Travis Brown