Composing Futures with For Comprehension

336 views Asked by At

I have a Play Framework application using ReactiveMongo with MongoDB, and I have the following code:

def categories(id: String): Future[Vector[Category]] = {...}
....
val categoriesFuture = categories(id)
for {
  categories: Vector[Category] <- categoriesFuture
  categoryIdsWithoutPerson: Vector[BSONObjectID] <- findCategoryIdsWithoutPerson(categories.map(_.id), personId) //Returns Future[Vector[BSONObjectID]]
  categoriesWithoutPerson: Vector[Category] <- categories.filter(category => categoryIdsWithoutPerson.contains(category.id)) //Play cites the error here
} yield categoryIdsWithoutPerson

To explain this code, I fetch a Vector of Categories wrapped in a Future because that's how ReactiveMongo rolls. In the for comprehension, I use that Vector to then fetch a list of ids from the database. Finally, I use a filter call to keep only those categories whose ids can be found in that id list.

It all seems fairly straightforward. The problem is that Play gives me the following compilation error on the last line of the for comprehension:

pattern type is incompatible with expected type;
 found   : Vector[com.myapp.Category]
 required: com.myapp.Category

I am not sure why the required type is a single instance of Category.

I could use some insight into what I am doing wrong and/or if there is a simpler or more idiomatic way of accomplishing this.

1

There are 1 answers

0
Noah On BEST ANSWER

It looks like you're trying to compose Futures with Vector. For comprehensions in scala have to all be of the same higher type, which in your case is Future. When you unroll the 'sugar' of the for comprehension, it's just calling flatMap on everything.

for {
  categories <- categoriesFuture
  // I'm not sure what the return type is here, but I'm guessing it's a future as well
  categoryIdsWithoutPerson <- findCategoryIdsWithoutPerson(categories.map(_.id), personId)
  // Here we use = to get around need to flatMap
  categoriesWithoutPerson = categories.filter(category => categoryIdsWithoutPerson.contains(category.id))
} yield categoryIdsWithoutPerson

Your code de-sugared:

categoriesFuture.flatMap(categories => 
  findCategoryIdsWithoutPerson(categories.map(_.id), personId).
    flatMap(categoryIdsWithoutPerson => 
       categories.filter(category => categoryIdsWithoutPerson.contains(category.id)).
          map(_ => categoryIdsWithoutPerson))