Reputation: 309
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
Reputation: 9820
flatMaps
and one map
function.Seq[...]
, Option[...]
, ...)Your working solution consists of two map
s 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 map
s, 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
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