John Doe John Doe - 2 months ago 9
Scala Question

How to wrap Scala for comprehension to resolve Future around Play's Form folding?

I have an action which processes form submissions. Before validating the form, I need to resolve two Futures. I thought I could just nest everything, meaning put the

fold
inside the
yield
block of the for comprehension.

For example:

def handleFormSubmission = silhouette.SecuredAction.async { implicit request =>
for {
user <- userService.findOneByUserId(userId)
avatar <- avatarService.findOneByUserId(userId)
} yield {
myForm.bindFromRequest.fold(
formWithErrors => formWithErrorsBranch(formWithErrors, user, avatar),
changeData => changeDataBranch(changeData, user, avatar))
}
}


Both branches return
Future[Result]
and the signature of
fold
is
def fold[R](hasErrors: Form[T] => R, success: T => R): R
. To my understanding,
fold
takes two functions with an argument
Form[T]
and
T
and both return
R
. That would mean that if I return in both branches
Future[Result]
,
fold
will also return
Future[Result]
. However, since it is wrapped inside a for comprehension to resolve both Futures
user
and
avatar
, I would not need to have
Future[Result]
but instead
Result
. Is that correct? If so, how can I fix the following compile error in a non-blocking manner?

type mismatch;
found : scala.concurrent.Future[play.api.mvc.Result]
required: play.api.mvc.Result

Answer

If I understand your problem correctly, this is how it can be solved:

  def handleFormSubmission = silhouette.SecuredAction.async { implicit request =>
    for {
      user <- userService.findOneByUserId(userId)
      avatar <- avatarService.findOneByUserId(userId)
      response <- myForm.bindFromRequest.fold(
        formWithErrors => formWithErrorsBranch(formWithErrors, user, avatar),
        changeData => changeDataBranch(changeData, user, avatar)
      )
    } yield response
  }

simply, don't yield so soon. If you are writing for comprehension and something is returning future, extract the value with <- and then yield it.

<- is translated into flatMap with signature (simplified) flatMap(f: A => Future[B]): Future[B]. So if your function returns a future and you don't want to get nested Future[Future[A]], use flatMap. yield is desugared to map with signature map(f: A => B): Future[B] so this is for the case when f does not return a Future unless you want it nested for some reason. If both of your branches for folding on form binding result return a Future[A] then you want to use flatMap.

On a side note, you are not obtaining user and avatar concurrently, but one after another. You might want to start the computations first and then "wait" for both to complete. This can be done in several ways, for example:

val userFuture = userService.findOneByUserId(userId)
val avatarFuture = avatarService.findOneByUserId(userId)
for {
  user <- userFuture
  avatar <- avatarFuture
  response <- ...
} yield response

or for example with zip

for {
  (user, avatar) <- userService.findOneByUserId(userId) zip avatarService.findOneByUserId(userId)
  response <- ...
} yield response