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

巧了我就是萌 提交于 2019-12-04 12:19:42

问题


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!


回答1:


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.



来源:https://stackoverflow.com/questions/23453570/using-shapeless-to-convert-tuple-of-future-to-future-of-tuple-by-way-of-hlist

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!