为Future与List创建单子转换器,简化for推导式使用
用单子转换器简化Future[Seq[T]]的遍历逻辑
完全可以通过自定义单子转换器FutureS来实现你想要的简洁for推导式,它本质是把Future和Seq两个单子组合起来,避免嵌套使用map和flatMap。下面是具体实现方案:
基于Cats库的标准实现
如果你的项目已经引入Cats(Scala生态中处理函数式编程的常用库),可以借助其Monad类型类来规范实现:
import scala.concurrent.{Future, ExecutionContext} import cats.Monad import cats.implicits._ // 自定义FutureS单子,封装Future[Seq[A]]类型 case class FutureS[A](value: Future[Seq[A]]) object FutureS { // 直接从Future[Seq[A]]构造FutureS def apply[A](fa: Future[Seq[A]]): FutureS[A] = FutureS(fa) // 将单个元素包装为FutureS def pure[A](a: A)(implicit ec: ExecutionContext): FutureS[A] = FutureS(Future.successful(Seq(a))) // 为FutureS实现Monad类型类实例 implicit def futureSMonad(implicit ec: ExecutionContext): Monad[FutureS] = new Monad[FutureS] { override def pure[A](a: A): FutureS[A] = FutureS.pure(a) override def flatMap[A, B](fa: FutureS[A])(f: A => FutureS[B]): FutureS[B] = { // 先解包Future,对Seq中每个元素应用f,再合并结果 val mapped: Future[Seq[Future[Seq[B]]]] = fa.value.map(_.map(f(_).value)) val flattened: Future[Seq[B]] = mapped.flatMap(_.sequence.map(_.flatten)) FutureS(flattened) } // 实现tailRecM避免递归栈溢出,Monad类型类的强制要求 override def tailRecM[A, B](a: A)(f: A => FutureS[Either[A, B]]): FutureS[B] = { val result: Future[Seq[B]] = f(a).value.flatMap { seqEither => seqEither.partitionMap(identity) match { case (Nil, bs) => Future.successful(bs) case (as, bs) => val futureSeqs: Seq[Future[Seq[B]]] = as.map(a => tailRecM(a)(f).value) futureSeqs.sequence.map(_.flatten ++ bs) } } FutureS(result) } } } // 定义Meta结构示例 case class Meta(database: String, table: String) // 原MetaClient调整为依赖ExecutionContext class MetaClient(implicit ec: ExecutionContext) { def getDatabases: Future[Seq[String]] = Future.successful(Seq("db1", "db2")) def getTables(database: String): Future[Seq[String]] = Future.successful(Seq(s"${database}_table1", s"${database}_table2")) def getMeta(database: String, table: String): Future[Meta] = Future.successful(Meta(database, table)) } object GetMeta extends App { implicit val ec: ExecutionContext = ExecutionContext.global val client = new MetaClient() // 使用for推导式串联异步操作 val metas: FutureS[Meta] = for { db <- FutureS(client.getDatabases) table <- FutureS(client.getTables(db)) // 将Future[Meta]转换为Future[Seq[Meta]]后包装为FutureS meta <- FutureS(client.getMeta(db, table).map(Seq(_))) } yield meta // 最终得到Future[Seq[Meta]],这比Seq[Future[Meta]]更实用 val result: Future[Seq[Meta]] = metas.value // 测试输出结果 result.onComplete { case scala.util.Success(metaList) => metaList.foreach(println) case scala.util.Failure(e) => e.printStackTrace() } // 如果确实需要Seq[Future[Meta]],可以从结果转换而来 val seqFutureMeta: Future[Seq[Future[Meta]]] = result.map(_.map(Future.successful)) }
轻量化手动实现(无依赖)
如果不想引入第三方库,也可以直接为FutureS实现flatMap和map方法,直接支持for推导式:
import scala.concurrent.{Future, ExecutionContext} case class Meta(database: String, table: String) case class FutureS[A](value: Future[Seq[A]]) { def flatMap[B](f: A => FutureS[B])(implicit ec: ExecutionContext): FutureS[B] = { val mapped = value.map(_.map(f(_).value)) val flattened = mapped.flatMap(_.sequence.map(_.flatten)) FutureS(flattened) } def map[B](f: A => B)(implicit ec: ExecutionContext): FutureS[B] = { FutureS(value.map(_.map(f))) } } object FutureS { def apply[A](fa: Future[Seq[A]]): FutureS[A] = FutureS(fa) def pure[A](a: A)(implicit ec: ExecutionContext): FutureS[A] = FutureS(Future.successful(Seq(a))) } // 使用方式与之前一致 class MetaClient(implicit ec: ExecutionContext) { def getDatabases: Future[Seq[String]] = Future.successful(Seq("db1", "db2")) def getTables(database: String): Future[Seq[String]] = Future.successful(Seq(s"${database}_table1", s"${database}_table2")) def getMeta(database: String, table: String): Future[Meta] = Future.successful(Meta(database, table)) } object GetMeta extends App { implicit val ec: ExecutionContext = ExecutionContext.global val client = new MetaClient() val metas = for { db <- FutureS(client.getDatabases) table <- FutureS(client.getTables(db)) meta <- FutureS(client.getMeta(db, table).map(Seq(_))) } yield meta val result: Future[Seq[Meta]] = metas.value result.onComplete { case scala.util.Success(metaList) => metaList.foreach(println) case scala.util.Failure(e) => e.printStackTrace() } }
关键说明
FutureS的核心作用是自动处理Future[Seq[A]]的嵌套结构,在for推导式中逐个提取Seq内的元素,避免手动嵌套flatMap- 注意
getMeta返回的是Future[Meta],需要先转换成Future[Seq[Meta]](通过map(Seq(_)))才能被FutureS包装 - 示例中最终返回的
Future[Seq[Meta]]是更实用的形式,它代表一个异步操作完成后得到所有Meta的集合;如果需要Seq[Future[Meta]],可以从该结果转换而来
内容的提问来源于stack exchange,提问作者synapse
相关产品推荐
相关产品推荐

