Required: scala.collection.GenTraversableOnce[?]

I’m a new scala developer kind of bogged down with a type problem. Sometimes I’m still tripped up by handling futures, and I think this is one of those times. This section of code…

// do some stuff with a collection of List[Future[ClientArticle]]
Future.sequence(listFutureClonedArticles).map( clonedArticles =>
	for {
		// create persistent records of the cloned client articles, and discard the response
		_ <- clonedArticles.map(clonedArticle => clientArticleDAO.create(clonedArticle))

		// add cloned articles to a batch of articles, and discard the response
		_ <- batchDAO.addArticlesToExistingBatch(destinationBatch._id, clonedArticles.map(_._id))

	} yield {

		// ultimately just return the cloned articles
		clonedArticles
	}
)

… is producing this compiler error:

[error] /.../app/services/BatchServiceAPI.scala:442: type mismatch;
[error]  found   : scala.concurrent.Future[List[model.ClientArticle]]
[error]  required: scala.collection.GenTraversableOnce[?]
[error] 							_ <- batchDAO.addArticlesToExistingBatch(destinationBatch._id, clonedArticles.map(_._id))
[error] 							  ^

The arguments to addArticlesToExistingBatch() appear to be the correct type for the method signature:

/** Adds a list of id's to a batch by it's database ID. */
def addArticlesToExistingBatch(batchId: ID, articleIds: List[ID])(implicit ec: ExecutionContext): Future[Return]

Of course, I might be misunderstanding how a for comprehension works too. I don’t understand how an error can occur at the <- operator, nor how/why there would be type expectations at that point.

Can anyone help me understand what needs to be done here?

The key thing is that, in a for comprehension, all of the types on the right-hand side of the arrows have to be the same, and if I’m understanding it correctly, that’s not the case here.

clonedArticles is a List[ClientArticle]. So when you call

clonedArticles.map(clonedArticle => clientArticleDAO.create(clonedArticle))

you’re winding up with List[Future[yourResponseType]], right? (I’m assuming that create() returns a Future.) That is essentially binding the for comprehension to List, so all of the clauses in it have to be List. But this:

batchDAO.addArticlesToExistingBatch(destinationBatch._id, clonedArticles.map(_._id))

is returning Future[List[ClientArticle]] – a Future, not a List.

So basically, you have to make those line up. I suspect the right answer is to put another Future.sequence around clonedArticles.map(), so that you have Future[List[yourResponseType]]. Once both clauses are Future, it should compile.

(At which point you will need to change the outer map to a flatMap, which suggests that your initial Future.sequence(listFutureClonedArticles) might as well come inside the for, as its first line.)

2 Likes

Yes, that makes a ton of sense.

Thank you so much; I feel like it’s unusual to find scala explanations that are as… comprehensible as yours : )

1 Like

I suspect you actually don’t need a for comprehension here. But just a block expression.

Thanks. I’m doing Scala training professionally these days, which is good practice.

Matter of taste, of course (for is never necessary), and it’s not obvious what the requirements are. This code is currently doing the inner two operations sequentially, which is safe but possibly overkill. If they are independent (which kind of depends on the database), then yes, they could be run in parallel instead, and composed together…