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

对着背影说爱祢 提交于 2019-12-03 07:36:36

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.

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