Scala for comprehension how to avoid creating of Future when passing results - scala

I'm using Playframework and Slick async features, but not sure how to work inline with results from Future returning method in one for comprehension. Right now I'm doing it such way:
def getWordDefinitions(checkedWordsIds: List[CheckedWord]) : Future[List[WordDefinition]] = {
val ids = checkedWordsIds.map(_.wordId)
for {
translations <- translationRepo.findByIds(ids)
translations2 <- Future(sortByHowManyChecks(checkedWordsIds, translations))
wordDefinitionsList <- Future(translations2.map(translation => WordDefinition(translation._2.english, translation._2.translation)))
} yield {
wordDefinitionsList
}
}
I want to know how to get rid off translations2 <- Future(), besides move it to the function (or wrap function into another which return Future).
sortByHowManyChecks function returns Map[Long, TranslationObject] which is in 3rd party library.

In your case you can simply write it this way:
def getWordDefinitions(checkedWordsIds: List[CheckedWord]) : Future[List[WordDefinition]] = {
val ids = checkedWordsIds.map(_.wordId)
for {
translations <- translationRepo.findByIds(ids)
translations2 = sortByHowManyChecks(checkedWordsIds, translations)
} yield translations2.map(translation => WordDefinition(translation._2.english, translation._2.translation))
}

What do you think about not using yield at all? Not sure if I got every return statement right.
def getWordDefinitions(checkedWordsIds: List[CheckedWord]) : Future[List[WordDefinition]] = {
val ids = checkedWordsIds.map(_.wordId)
translationRepo.findByIds(ids)
.map(translations => sortByHowManyChecks(checkedWordsIds, translations))
.map(translation => WordDefinition(translation._2.english, translation._2.translation))
}

Related

For comprehension that has to handle 2 optional values and return a Option[T]

The below code works fine, but as you can see the 2nd clause in the for comprehension has a call that is unsafe.
case class ProductView(product: Product, stores: List[Store], warehosue: Option[Warehosue])
def loadView(...): ConnectionIO[Option[ProductView]] =
for {
product <- getProductById(id) // ConnectionIO[Option[Product]]
warehouse <- getWarehouse(product.get.warehouseId.get.id) // ConnectionIO[Option[Warehouse]]
stores <- loadStores(...) // ConnectionIO[List[Store]]
} yield product map { p =>
ProductView(p, stores, warehouse)
}
I tried to make that a safe call, but my types don't seem to line up.
warehouse <- getWarehouse(product.get.warehouseId.get.id)
How can I improve this, if any of the options is a None, I just want to return a None.
This is suppose to return a Option[Warehouse]
I tried this:
warehouse <- product.map(p => p.warehouseId.map(id => getWarehouse(id)))
Hoping someone can help with this part of my for comprehension.
The easiest way is using OptionT and flatTraverse
def loadView(id: Int):
ConnectionIO[Option[ProductView]] =
(for {
product <- OptionT(getProductById(id))
warehouse <- OptionT.liftF(product.warehouseId.flatTraverse(getWarehouse))
stores <- OptionT.liftF(loadStores(...))
} yield ProductView(product, stores, warehouse)).value
Also alternative variant without OptionT
def loadView(id: Int): ConnectionIO[Option[ProductView]] = {
getProductById(id).flatMap {
_.traverse { product =>
for {
warehouse <- product.warehouseId.flatTraverse(getWarehouse)
stores <- loadStores(...)
} yield ProductView(product, stores, warehouse)
}
}
}

Execute operation on Future's value for its side effects, discarding result and retaining original value, but retaining order of operation

Say I have the following operations that must proceed in order:
Get blog post
Post analytics
Forward blog post
In code it may look like this:
val blogPostFut: Future[BlogPost] = blogService.getPost(postId)
val afterAnalytics: Future[BlogPost] = blogPostFut.flatMap(blogPost =>
val ignoredResponse: Future[Analytics] = analyticsService.sendAnalytics(blogPost)
ignoredResponse.map(_ => blogPost) // <-- THIS BOTHERS ME
)
val finalValue: Future[ForwardResult] = afterAnalytics.flatMap(blogPost =>
forwardService.forward(blogPost)
)
I am bothered that, in order to ensure proper ordering of execution, I have to pass forward blogPost within ignoredResponse in order to ensure it is available for step 3.
I'd love if I could do something like this:
blogPostFut.magicalFlatMap(analyticsService.sendAnalytics)
Where magicalFlatMap might be implemented like so:
// pseudocode
def magicalFlatMap[A,B](f: A => Future[B]): Future[A] = f().map(_ => this.value)
Does magicalFlatMap exist in either the Scala stdlib or in Cats? Is it possible to map a Future for side effects while automatically retaining the value of the original Future and strict ordering of operations?
magicalFlatMap seems to be cats.FlatMap#flatTap
https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/FlatMap.scala#L150
Try Future.andThen for side-effects
for {
blogPost <- blogService.getPost(postId).andThen { case Success(post) => analyticsService.sendAnalytics(post) }
finalValue <- forwardService.forward(blogPost)
} yield {
finalValue
}
Here is a dummy example
val result = for {
v1 <- Future(1)
v2 <- Future(v1 + 2).andThen { case Success(v) => println(v) }
v3 <- Future(v1 + v2)
} yield {
v3
}
result.foreach(println)
which should output
3
4
We could also do
for {
blogPost <- blogService.getPost(postId)
_ <- analyticsService.sendAnalytics(blogPost)
finalValue <- forwardService.forward(blogPost)
} yield {
finalValue
}
however in this case failure in analyticsService.sendAnalytics(blogPost) would short-circuit the whole for-comprehension which might not be desirable.

How to convert a class T object into a Future[T] object in akka

I am trying to do something with akka and scala and i am new to it. I want to append the result of an 'Any' Future into one of the fields in the class
So i have class called T defined as
class T {
var a: String =_
var result = List[Any]= List()
}
Now i have a variable which receives a future value
var futureResult:Future[Any] = //receives a future value
in a function called addResult which takes an T object and returns a future T object.
def addResult(obj1:T):Future[T] ={
var obj2:T = new T()
obj2.a = obj1.a
obj2.result = obj1.result
//I want to append results of futureResult into obj2.result when it is completed
// obj2.result = futureResult :: obj2.result
return Future(obj2)
}
I have to finally call this function from a flow.
val b = Flow[T].mapAsync[T](1)(addResult(_))
First, as #riccardo.cardin noted using T as a name of a class is very bad idea, because of T usual points to generic.
However, you can put any logic in Future with a simple closure, in your case, it looks like this:
Future {
new T("some string", 1 :: 2 :: 3 :: Nil)
}
After that, you can combine asynchronous computation via flatMap, map or with for comprehensive or even use cats black magic with |#|:
for {
f1 <- future1()
f2 <- future2(f1.id)
} yield f2.name
(future1 |#| future2) map { _ - _ }
In your case this transformation depends from logic of your code.
You can change the commented line like this:
futureResult.map { res =>
obj2.result = res :: obj2.result
obj2
}
And then you won't need the last line.

Scala : Better way to handle returning Future from Yeild to avoid future of future

Consider the below code snippet:
In this, I am trying to get values from future using 'For - yield' comprehension. Now in yield method, I need to do a check which makes the call to a function fallbackResult which returns a future and hence return type of getData becomes 'Future[Future[Option[Int]]]' rather than 'Future[Option[Int]]'. How could I make this with a better way? (I did use Map & FlatMap's, but the code little ugly due to nesting Maps and FlatMaps)
def getData(): Future[Future[Option[Int]]] = {
/* These are two future vectors. Ignore the Objects */
val substanceTableF: Future[Vector[OverviewPageTableRowModel]] = getSubstanceTable(substanceIds, propertyId, dataRange)
val mixtureTableF: Future[Vector[OverviewPageTableRowModel]] = getMixtureTableForSubstanceCombination(substanceIds, propertyId, dataRange)
/* I have put for yeild to get values from futures.*/
for {
substanceTable <- substanceTableF
mixtureTable <- mixtureTableF
} yield {
if(substanceTable.isEmpty && mixtureTable.isEmpty) {
val resultF = fallbackResult()
resultF.map(result => {Some(result)})
} else {
Future.successful(Some(100))
}
}
}
private def fallbackResult(): Future[Int] = {
// This method returns future of int
}
There are a lot of ways to handle this, but the key thing is to move your yield logic into your for comprehension. One way to do this is as follows:
for {
substanceTable <- substanceTableF
mixtureTable <- mixtureTableF
result <- (substanceTable.headOption orElse mixtureTable.headOption)
.map(_ => Future.successful(Some(100)))
.getOrElse(fallbackResult)
} yield result
Id put the code inside the for-coprehension:
for {
substanceTable <- substanceTableF
mixtureTable <- mixtureTableF
result <- {
if (substanceTable.isEmpty && mixtureTable.isEmpty)
fallbackResult()
else
Future.successful(Some(100))
}
} yield result

How to convert this to using Futures, not sure how to use Future.Traverse

I have the following old code that was previously NOT returning futures, but it is now:
for (rg <- effectedRoleGroups) {
val pm = securityService.generateABC(rg.id)
redisService.setABC(rg.id, pm)
}
Now both securityService and redisService are returning Futures.
I know I can do this:
for {
result <- Future.traverse(effectedRoleGroups)(rg -> securityService.generateABC(rg.id)
}
But I am not sure how I would use the result in this case to pass to my other service.
Asuming you want to ignore result of Futures as you use foreach loop you can do this like this:
for (rg <- effectedRoleGroups) {
for {
pm <- securityService.generateABC(rg.id)
_ <- redisService.setABC(rg.id, pm)
} yield ()
}
Which is equivalent to:
for (rg <- effectedRoleGroups) {
securityService.generateABC(rg.id).flatMap(pm => redisService.setABC(rg.id, pm))
}
Using Future.traverse:
val results: Future[List[ResultFromRedis]]] = Future.traverse(effectedRoleGroups)(rg =>
securityService.generateABC(rg.id).flatMap(pm => redisService.setABC(rg.id, pm)))
This will give you Future of List of results that redisService.setABC will yield.
Future.traverse(..some traversable..)(...)
returns Future of transformed traversable.
So you actually don't need to use for here. Instead you can do:
val resultFuture = Future.traverse(effectedRoleGroups)(rg -> securityService.generateABC(rg.id))
If you still want to use for you need yield operator:
val resultFuture = for {
result <- Future.traverse(effectedRoleGroups)(rg -> securityService.generateABC(rg.id))
} yield result