How to map an Option inside a for comprehension with EitherT - scala

Hi I am trying to execute a for comprehension like
(for {
player <- playerRepository.findById(playerId) // findById returns EitherT[Future, String, Player]
teamOpt <- teamRepository.findByPlayer(playerId) // findByPlayer returns EitherT[Future, String, Option[Team]]
playedMatches <- teamOpt.map(team => playedMatchesRepository.findByTeamId(team.id)) // findByTeamId returns EitherT[Future, String, Seq[PlayedMatches]]
} yield (
player,
teamOpt,
playedMatches
)).fold(
error => {
logger.error(s"all error: $error")
Left(error)
},
tuple => {
logger.debug(s"get success -> $tuple")
Right(playerDetailResponse(tuple._1, tuple._2, tuple._3))
}
)
I can not get a corret structure for
playedMatches <- teamOpt.map(team => playedMatchesRepository.findByTeamId(team.id))
I am getting the following error when I compile the project
[error] /Users/agusgambina/code/soccer/app/services/impl/PlayerServiceImpl.scala:28:17: type mismatch;
[error] found : Option[(models.Player, Option[models.Team], cats.data.EitherT[scala.concurrent.Future,String,Seq[models.PlayedMatches]])]
[error] required: cats.data.EitherT[scala.concurrent.Future,?,?]
[error] playedMatches <- teamOpt.map(team => playedMatchesRepository.findByTeamId(team.id))
[error] ^
[error] one error found
I tried to wrap

playedMatches <- teamOpt.map(team => playedMatchesRepository.findByTeamId(team.id)) // findByTeamId returns EitherT[Future, String, Seq[PlayedMatches]]
in here, you are getting an Option[EitherT[Future, String, Seq[PlayedMatches]]] which doesn't compose with the EitherT[Future, String, ???] you are using as Monad for the for comprehension.
one option you have is to actually use a fold on teamOpt.
teamOpt.fold(EitherT(Future.successful(Left("Team not Found"): Either[String, Team]))){ team => playedMatchesRepository.findByTeamId(team.id) }
This way you unwrap the Option with the error case if is empty or the success case if non-empty. (create a function that takes the teamOPt as parameter and the for-comprehension will look much better)
Hope it helps
update
In case of the empty case be successful, and be happy returning an empty sequence:
teamOpt.fold(
EitherT(Future.successful(Right(Seq()): Either[String, Seq[PlayedMatches]))
){ team =>
playedMatchesRepository.findByTeamId(team.id)
}

Related

“required: scala.collection.GenTraversableOnce[?]” error from this for-comprehension

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?
=== 21 minutes later... ===
Interesting. When I stop using the for comprehension and break these out into two separate maps, it compiles.
// create cloned client articles
Future.sequence(listFutureClonedArticles).map(clonedArticles =>
clonedArticles.map(clonedArticle => clientArticleDAO.create(clonedArticle)))
// add cloned articles to destination batch
Future.sequence(listFutureClonedArticles).map(clonedArticles =>
batchDAO.addArticlesToExistingBatch(destinationBatch._id, clonedArticles.map(_._id)))
So yeah, I guess I still don't quite understand for-comprehensions. I thought they could be used to roll up several Future operations. Why doesn't that work in this scenario
for comprehension is a combination of flatMap and map. Every line with <- is converted into a flatMap but the last line which is converted to a map.
So, you code
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 converted to
clonedArticles.map(clonedArticle => clientArticleDAO.create(clonedArticle)).flatMap { _ =>
batchDAO.addArticlesToExistingBatch(destinationBatch._id, clonedArticles.map(_._id)).map { _ =>
clonedArticles
}
}
as clonedArticles is a List and the signature for flatMap for the list is
final override def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[List[A], B, That]): That = ???
If you look at the parameter required by flatMap it needs a function A => GenTraversableOnce but in your function you are passing a function A => Future that is the problem.
I have tried to imitate your problem with simple functions you can try that:
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
val listOfFuture: List[Future[Int]] = (1 to 10).map(Future(_)).toList
def f(i: List[Int]): Future[String] = Future(s"very complex logic: ${i.sum}")
def create(i: Int): Future[Unit] = Future(println(s"creating something complex: $i"))
Future.traverse(listOfFuture){ futureX =>
for {
x <- futureX
_ <- create(x)
} yield x
}.flatMap(f)

Return type of bindFromRequest.fold

My feeble Scala skills have me baffled about the right way to do something. The code below compiles (and works) only when I include the t match {... line. If I eliminate that line and of course, the diagnostic println on the preceding line, I get the compile time error as shown. Apparently the compiler regards the return of the fold as Unit (surprise to me). That might be reasonable, but I don't understand it. Would someone please enlighten me about a better way to code this, and perhaps give me more insight?
[error] /home/bill/activator/cherry/app/controllers/Application.scala:34: type mismatch;
[error] found : Unit
[error] required: play.api.mvc.Result
[error] }
[error] ^
Source:
def ptweets = Action { implicit request =>
import play.api.data._
val rqForm = Form(Forms.mapping(
"k" -> Forms.number,
"who" -> Forms.text,
"what" -> Forms.text)(TweetInquiry.apply)(TweetInquiry.unapply))
val t = rqForm.bindFromRequest.fold(
formWithErrors => BadRequest("That's not good"),
rq => Ok((views.html.properForm("POST tweets TBD.")(Html("<em>Blah</em>"))))
) // expect a play.api.mvc.Result
println(t.getClass.getName) // this confirms it in both run-time cases
t match { case v:Result => v } // yet this is required for compile
}
as m-z said in the comments, change
val t = rqForm.bindFromRequest.fold(
formWithErrors => BadRequest("That's not good"),
rq => Ok((views.html.properForm("POST tweets TBD.")(Html("<em>Blah</em>"))))
) // expect a play.api.mvc.Result
println(t.getClass.getName) // this confirms it in both run-time cases
t match { case v:Result => v } // yet this is required for compile
to just:
rqForm.bindFromRequest.fold(
formWithErrors => BadRequest("That's not good"),
rq => Ok((views.html.properForm("POST tweets TBD.")(Html("<em>Blah</em>"))))
)
The fold is evaluating to a Result, but in the code you posted, you're assigning that Result to the value t. Thus, instead of the Action block evaluating to the result of the fold, it's evaluating to an assignment (which is Unit, see here).

Error on return a Future[Boolean] from a for in Scala

I'm writing a Play 2.3.2 application in Scala.
I use reactivemongo as driver for my MongoDB database.
I've a collection named "recommendation.tagsSimilarity", that contain the value of the similarity between my tags, where a tag is in the form :"category:attribute".
An example of a document is like the following:
{
"_id" : ObjectId("5440ec6e4165e71ac4b53a71"),
"id" : "10912199495810912197116116114-10912199581091219711611611450",
"tag1" : "10912199495810912197116116114",
"tag1Name" : "myc1:myattr",
"tag2" : "10912199581091219711611611450",
"tag2Name" : "myc:myattr2",
"eq" : 0
}
A doment represents an element of a matrix of nxn dimensions, where n is the number of tags saved.
Now I've created a collection named "recommendation.correlation" on which i save the correlation between a "category" and a tag.
For do that I'm writing a method that iterate on the elements of the TagSimilarity as a matrix.
def calculateCorrelation: Future[Boolean] = {
def calculate(category: String, tag: String): Future[(Double, Double)] = {//calculate the correlation and return the tuple value
}
play.Logger.debug("Start Correlation")
Similarity.all.toList flatMap { tagsMatch =>
for(i <- tagsMatch) {
val category = i.tag1Name.split(":")(0) // get the tag category
for(j <- tagsMatch) {
val productName = j.tag2Name //obtain the product tag
calculate(category, productName) flatMap {value =>
val correlation = Correlation(category, productName, value._1, value._2) //create the correlation object
val query = Json.obj("category" -> category, "attribute" -> productName)
Correlations.update(query, correlation, upsert = true) flatMap{status => status match {
case LastError(ok, _, _, _, _, _, _) => Future{true}
case _ => Future{false}
}}
}
}
}
}
}
But the compiler gives me the following error:
[error] /Users/alberto/git/bdrim/modules/recommendation-system/app/recommendationsystem/algorithms/Pearson.scala:313: type mismatch;
[error] found : Unit
[error] required: scala.concurrent.Future[Boolean]
[error] for(i <- tagsMatch) {
[error] ^
[error] /Users/alberto/git/bdrim/modules/recommendation-system/app/recommendationsystem/algorithms/Pearson.scala:313: type mismatch;
[error] found : Unit
[error] required: scala.concurrent.Future[Boolean]
[error] for(i <- tagsMatch) {
[error] ^
[error] one error found
What's wrong?? I can't understand why the for statement don't return nothing.
In addition to I want to ask why i can't write the code in a for comprehension in Scala for iterate two times on the list.
You forgot to use yield with for:
for(i <- tagsMatch) { ... } gets translated to a foreach instruction.
Using for(i <- tagsMatch) yield { ... } it will actually translate to map/flatMap and yield a result (remember to use it on both of your fors).

Finding a possible slug in mongo using reactivemongo

I am trying to find an available slug in mongodb using Play2 and reactivemongo.
I came up with the following recursive method.
private def findSlug(base: String, index: Int): String = {
val slug: String = Slug({
base + "-" + index
})
for {
user <- findOne(BSONDocument("slug" -> slug))
} yield {
user match {
case None => slug
case Some(user) => findSlug(base, index+1)
}
}
}
I get the following error
found : scala.concurrent.Future[String]
required: String
user <- findOne(BSONDocument("slug" -> slug))
^
I played around a lot with changing return types, mapping the result of the yield, etc. but somehow I think there might be a far simpler, concise and correct solution.
It all boils down to having a recursive function that calls another asynchronous function I think.
If I change the return type of findSlug to Future[String] I get
[error] found : scala.concurrent.Future[String]
[error] required: String
[error] case Some(user) => findSlug(base, index+1)
[error] ^
What would be the correct and idiomatic solution?
I personally think that making the return type a Future[String] is the right way to go - it means you can keep that whole "Reactive", monadic, chain-of-Futures thing going for as long as possible (and with Play 2, that can be all the way to the Controller level - awesome).
I'm assuming that your findOne() method is just a wrapper for some Reactive Mongo query that will return a Future[Option[T]] at some point?
With that in mind, the only real problem is that there are differing levels of Futurey-ness coming from your two different cases.
Here's what I came up with. It works; is it idiomatic? You tell me ...
private def findSlug(base: String, index: Int): Future[String] = {
// Slug stuff omitted
findOne(BSONDocument("slug" -> slug)).flatMap { _ match {
case None => Future.successful(slug)
case Some(_) => findSlug(base, index+1)
}
}
}

how do you destructure a list in scala

I have this code:
val host:String = Play.configuration.getString("auth.ldap.directory.host").get
val port:java.lang.Integer = Play.configuration.getString("auth.ldap.directory.port").get.toInt
val userDNFormat:String = Play.configuration.getString("auth.ldap.userDNFormat").get
to which I need to add a dozen more config options, so I was hoping to refactor it to something like:
val params = Seq("auth.ldap.directory.host", "auth.ldap.directory.port", "auth.ldap.userDNFormat")
params.map(Play.configuration.getString) match {
case host~port~userDNFormat => foo(host, port, userDNFormat)
}
I made that code up. What is the proper syntax to do this? On the map/match line I get this error, which I do not understand:
error: type mismatch;
found : (String, Option[Set[String]]) => Option[String]
required: java.lang.String => ?
in order to match on a sequence, you can write
case Seq(host, port, userDNFormat) => foo(host, port, userDNFormat)