user2232887
user2232887

Reputation: 309

For comprehension without calling flatmap

Is there a more idiomatic way of writing repeated calls to map (without using flatMap)?

Please see the following example:

val futureJson : Future[Seq[JsValue]] = futureBlogList.map(
                                            blogList => blogList.map(
                                                    blog => Json.obj("id" -> blog.id, "title" -> blog.title)))

val futureResult : Future[Result] = futureJson.map(jsList => Ok(JsArray(jsList)))

This is a function in Play which needs to return in this case a Future[Result].

I have tried using "for comprehension" but have not found an application for flatMap. My attempt using flatMap leaves me with Future[Nothing].

val futureJson : Future[Nothing] = for {
  blogList : Seq[Blog] <- futureBlogList
  blog : Blog       <- blogList
} yield {
  Json.obj("id" -> blog.id, "title" -> blog.title)
}

Upvotes: 2

Views: 312

Answers (2)

Peter Neyens
Peter Neyens

Reputation: 9820

  • A for comprehension is always a combination of a number of flatMaps and one map function.
  • The functions on the right side of the arrow in a for comprehension, always have the same result type (Seq[...], Option[...], ...)

Your working solution consists of two maps and a Future and a Seq so it cannot be converted into a for comprehension (nicely).

More information about for comprehensions (and the conversion with flatMap/map) can be found in Programming in Scala, 1ed and this question.


If you realy want to use a for comprehensions instead of the two maps, we can (as an educational exercise) convert the first map into a flatMap. The function using the blogList should then return a Future:

futureBlogList.flatMap(blogList =>
  Future.successful(
    blogList.map(blog => Json.obj("id" -> blog.id, "title" -> blog.title))
  )
)

We can convert the second map to a for expression:

futureBlogList.flatMap(blogList =>
  Future.successful(
    for (blog <- blogList) yield Json.obj("id" -> blog.id, "title" -> blog.title)
  )
)
//.map(identity)

Which we can turn into the following (pretty ugly) for comprehension:

for {
  blogList <- futureBlogList
  jsonBlogs <- Future.successful(
    for (blog <- blogList) yield Json.obj("id" -> blog.id, "title" -> blog.title)
  )
) yield jsonBlogs

Upvotes: 3

Arne Claassen
Arne Claassen

Reputation: 14414

The reason your for-comprehension fails is that it has work on containers of the same type. In your example you are mixing a Future with a collection. True, both have map but they are different types of containers and therefore their map/flatmap chains cannot interoperate and hence cannot be used together in a for-comprehension. If you unrolled the for-comprehension you would get

futurBlogList.flatMap { blogList =>
  blogList.map { blog =>
    Json.obj("id" -> blog.id, "title" -> blog.title)
  }
}

The flatMap expects the result to be a Future[U] but the map call inside returns a Seq[JSList] instead.

The end result is that your nesting of two different types of map is the most idiomatic way to express the construct. At best you could combine the two calls into something like this:

val futureResult : Future[Result] = futureBlogList.map { blogList =>
  Ok(JsArray(
    blogList.map( blog => Json.obj("id" -> blog.id, "title" -> blog.title))
  ))
}

Upvotes: 1

Related Questions