0

There's an asynchronous client that returns Futures of Lists. To iterate over results I need to use nested map and flatMap. Is there a way to create a monad transformer to be able to use simpler for-comprehensions, so it would look something along the lines of

class MetaClient {
  def getDatabases: Future[Seq[String]] = ???
  def getTables(database: String): Future[Seq[String]] = ???
  def getMeta(database: String, table: String): Future[Meta] = ???
}

object GetMeta {
  val client = new MetaClient()
  
  val metas = for {
    db <- FutureS(client.getDatabases)
    table <- FutureS(client.getTables(db))
    meta <- FutureS(client.getMeta(db, table))
  } yield meta
  
  val result: Seq[Future[Meta]] = metas.run()
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
synapse
  • 5,588
  • 6
  • 35
  • 65
  • 2
    Yes there's a way. Is it worth it? I'm not sure. I mean do you have a specific question on implementing it or are you looking for us to implement it? – Gaël J Jan 22 '23 at 06:54
  • 3
    You can use `Future.sequence` to transform a `Seq[Future[A]]` into `Future[Seq[A]]`, so you can just keep on `fut.flatMap(as => Future.sequence(as.map(toFuture)))`. – Mateusz Kubuszok Jan 22 '23 at 10:12
  • https://stackoverflow.com/questions/27454798/is-future-in-scala-a-monad – Dmytro Mitin Jan 23 '23 at 05:04
  • https://stackoverflow.com/questions/42290243/which-monad-transformer-to-use "`Future` does not have a monad transformer (it's not traversable and it's impossible to implement `FutureT` without blocking)" https://medium.com/@alexander.zaidel/monad-transformers-arent-hard-23387c7ef4a6 https://gist.github.com/teazaid/56e6a03bd70f1797ffd5332b5c9b98c9#file-futuret-scala "`FutureT` is blocking a thread of execution, because `Future[F[Future[B]]]` can’t easily be converted to `F[Future[Future[B]]]`. And I guess this is one of the reasons we don’t see anyone using it." – Dmytro Mitin Jan 23 '23 at 05:42

1 Answers1

0
val metas: Future[Seq[Meta]] = for {
  dbs <- client.getDatabases
  tables <- Future.traverse(dbs)(client.getTables)
  tablesWithDbs  = dbs.zip(tables).flatMap{ case (db, tables) => tables.map((db, _))}
  meta <- Future.traverse(tablesWithDbs)((client.getMeta _).tupled)
} yield meta

I think this is quite simpler and less magical= more maintainable

V-Lamp
  • 1,630
  • 10
  • 18