change function call sequence based on a request parameter - scala

I have a situation similar to below where if request is coming from the UI then request must be validated first before doing anything else. However if request wasn't submitted from the UI - but say via EDI -> in this case there are some biz req how a child data "id" is populated in the request after persisting parent data and use parent data id into child data section. that detail isn't important for this question.
In order to change order of method calls inside a for comprehension I have something similar to below which looks bit repetitive and non idiomatic . is there a better way to achieve this?
def persistData(req : Request) = {
req.actionFromUI match{
case Some(_) => for{
validatedReq <- validateRequest(req) //1st thing
transformedReq <- transformRequest(validatedReq)
persitedReq <- persistRequestData(transformedReq)
}
case None => for{
transformedReq <- transformRequest(validatedReq)
persitedReq <- persistRequestData(transformedReq)
validatedReq <- validateRequest(persitedReq) //last thing
}
}
}

How about something like:
def persistData(req : Request) = {
val (c1, c2, c3) = req.actionFromUI match{
case Some(_) => (validateRequest(req),
transformRequest(validatedReq),
persistRequestData(transformedReq))
case _ => (transformRequest(validatedReq),
persistRequestData(transformedReq),
validateRequest(persitedReq))
}
for {
f1 <- c1
f2 <- c2
f3 <- c3
} // .. do something here with f1, f2, f3
}

Something like this maybe (assuming your validateRequest returns an Option[Request])?
val (preValidate, postValidate) = req.actionFromUI match {
case Some(_) => (validateRequest _, Option[Request].apply _)
case _ => (Option[Request].apply _, validateRequest _)
}
for {
pre <- preValidate(req)
transformed <- transformRequest(pre)
persisted <- persistRequest(transformed)
post <- postValidate(persisted)
}
Or alternatively
def validateRequest(req: Request, pre: Boolean) =
if(req.actionFromUI.isDefined == pre) validateRequest(req) else Some(req)
for {
pre <- validateRequest(req, true)
transformed <- transformRequest(pre)
persisted <- persistRequest(transformed)
post <- validateRequest(persisted, false)
}

Related

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.

Scala for/yield with multiple optional calls based on credentials

I need to get an entire case class, built with 3 other case classes, back in one call. However, depending on a users credentials they may or may not have access to certain parts of that case class.
Below is how I'm currently doing it.(and it works) It's making the call but clearing out the unauthorized case classes afterwards. I would ideally like to stop the call (due to low bandwidth) if they don't have the proper credentials and return None.
The persistent entity service call is expecting an Option[PersonOne] and Option[PersonTwo] so the .ask must return that or it says it's returning an object and won't compile.
override def getPerson(id: UUID, credOne: Option[Boolean], credTwo: Option[Boolean]) = ServerServiceCall { _ =>
for {
g <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral)
h <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonOne) //if (credOne) **this 'if' does not work**
s <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonTwo)
} yield {
val x: Option[PersonOne] = if (credOne == Some(true)) h else None
val y: Option[PersonTwo] = if (credTwo == Some(true)) s else None
CompletePerson(g.get, x, y)
//TODO catch if no employee is returned
//throw NotFound (s"Person not Found with $id");
}
}
Here is a version of your function that will only call ask if the appropriate credential value is Some(true):
override def getPerson(id: UUID, credOne: Option[Boolean], credTwo: Option[Boolean]) = ServerServiceCall { _ =>
CompletePerson(
registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral).get,
credOne.filter(_ == true).flatMap(_ => registry.refFor[PersonEntity](id.toString()).ask(GetPersonOne)),
credTwo.filter(_ == true).flatMap(_ => registry.refFor[PersonEntity](id.toString()).ask(GetPersonTwo))
)
}
The key part is this:
credX.filter(_ == true).flatMap(...)
If credX is None this will return None
If credX contains false it will return None
If credX is Some(true) then flatMap will call the ask function
If ask returns None then the expression will return None, otherwise it will return Some[PersonX]
I am a bit unclear on some of the data types, but I think this should give you some idea of how to approach this code. (For example, that bare .get look dangerous as it could throw an exception)
Edit after comments
I fixed an issue with the filter(_), it should be filter(_ == true).
It looks like ask actually returns Option[Option[T]], in which case this might be closer to what is needed.
def getPerson(id: UUID, credOne: Option[Boolean], credTwo: Option[Boolean]) = ServerServiceCall { _ =>
for {
g <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral)
gen <- g
} yield {
val p1 = credOne.filter(_ == true).flatMap(_ => registry.refFor[PersonEntity](id.toString()).ask(GetPersonOne))).flatten
val p2 = credTwo.filter(_ == true).flatMap(_ => registry.refFor[PersonEntity](id.toString()).ask(GetPersonTwo))).flatten
CompletePerson(gen, p1, p2)
}
}
I'm not quite sure what you're asking, but are you concerned that if
registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral)
returns None that you'll keep making expensive .ask calls? Cos that won't happen, as we can see from this
def giveMeSome = {println("giving you Some"); Some(1)}
def giveMeNone = {println("giving you None"); None}
def giveMeSomeMore = {println("giving you Some more"); Some(2)}
val ans = for {
a <- giveMeSome
f <- giveMeNone
q <- giveMeSomeMore
} yield (a, f, q)
println(ans)
which prints
giving you Some
giving you None
None
We can also see that in this case, you'll get None back. I can't tell if you want a None or an error thrown. If you want an error, you can just wrap what's above
I didn't get your question completely, but based on my understanding you want to first check if credOne == Some(true) && credTwo == Some(true) then proceed further else return None
override def getPerson(id: UUID, credOne: Option[Boolean], credTwo: Option[Boolean])
=
for {
cone <- credOne
ctwo <- credTwo
if cone
if ctwo
} yield {
ServerServiceCall { _ =>
for {
g <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral)
h <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonOne)
s <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonTwo)
if g.isDefined
} yield {
CompletePerson(g.get, h, s)
}
}

Conditionally running Slick statements in a for comprehension

Given the following Slick code:
val doUpdate = true
val table1 = TableQuery[Table1DB]
val table2 = TableQuery[Table2DB]
val table3 = TableQuery[Table3DB]
val action = (for {
_ <- table1.filter(_.id === id).update(table1Record)
_ <- table2.filter(_.id === id).update(table2Record)
_ <- table3.filter(_.id === id).update(table3Record)
} yield ()).transactionally
What I need is to update table2 and table3 ONLY if the variable doUpdate is true. This is my attempt:
val action = (for {
_ <- table1.filter(_.id === id).update(table1Record)
_ <- table2.filter(_.id === id).update(table2Record)
if (doUpdate == true)
_ <- table3.filter(_.id === id).update(table3Record)
if (doUpdate == true)
} yield ()).transactionally
This compiles, but when I run it I get the following error Action.withFilter failed. How to add the condition?
As you notices if you use if inside a for it will call withFilter underneath. In case of pipelines (DBIO, Futures, Tasks) that filtering boils down to whether or not continue with a pipeline (happy path) or fail it (generating Failure or similar).
If all you want is to update, then you can go with the if road and recover the Future. However, it would we ugly, since depending on how you write the case it might also recover from errors you would like to be informed about.
Alternative is to simply use if-else to provide a different DBIOAction:
(for {
_ <- {
if (doUpdate) conditionalAction.map(_ => ())
else DBIOAction.successful(())
}
} yield ()).transactionally
In your case it could look something like:
val action = (for {
_ <- table1.filter(_.id === id).update(table1Record)
_ <- {
if (doUpdate) DBIOAction.seq(
table2.filter(_.id === id).update(table2Record),
table3.filter(_.id === id).update(table3Record)
)
else DBIOAction.successful(())
}
} yield ()).transactionally

Scala's for comprehension for Futures and Options

I have recently read Manuel Bernhardt's new book Reactive Web Applications. In his book, he states that Scala developers should never use .get to retrieve an optional value.
I want to pick up his suggestions but I am struggling to avoid .get when using for comprehensions for Futures.
Let's say I have the following code:
for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
userId <- user.id
_ <- accountTokenService.save(AccountToken.create(userId, email))
} yield {
Logger.info("Foo bar")
}
Normally, I would have used AccountToken.create(user.id.get, email) instead of AccountToken.create(userId, email). However, when trying to avoid this bad practice, I get the following exception:
[error] found : Option[Nothing]
[error] required: scala.concurrent.Future[?]
[error] userId <- user.id
[error] ^
How can I solve this?
First option
If you really want to use for comprehension you'll have to separate it to several fors, where each works with the same monad type:
for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
} yield for {
userId <- user.id
} yield for {
_ <- accountTokenService.save(AccountToken.create(userId, email))
}
Second option
Another option is to avoid Future[Option[T]] altogether and use Future[T] which can materialize into Failure(e) where e is a NoSuchElementException whenever you expect a None (in your case, the accountService.save() method):
def saveWithoutOption(account: Account): Future[User] = {
this.save(account) map { userOpt =>
userOpt.getOrElse(throw new NoSuchElementException)
}
}
Then you'll have:
(for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.saveWithoutOption(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
_ <- accountTokenService.save(AccountToken.create(user.id, email))
} yield {
Logger.info("Foo bar")
}) recover {
case t: NoSuchElementException => Logger.error("boo")
}
Third option
Fall back to map/flatMap and introduce intermediate results.
Let's take a step back and explore the meaning of our expression:
A Future is "eventually a value (but might fail)"
An Option is "maybe a value"
What are the semantics of Future[Option]? Let's explore the values to gain some intuition:
Future[Option]
Success(Some(x)) => Good. Let's do stuff with x
Success(None) => Finished but got nothing => This is probably an application-level error
Failure(_) => Something went wrong, so we don't have a value
We can flatten Success(None) into a Failure(SomeApplicationException) and eliminate the need of handling the Option separately.
For that, we can define a generic function to turn an Option into a Future and use the for-comprehension to apply the flattening.
def optionToFuture[T](opt:Option[T], ex: ()=>Exception):Future[T] = opt match {
case Some(v) => Future.successful(v)
case None => Future.failed(ex())
}
We can now express our computation fluently with a for-comprehension:
for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
userId <- optionToFuture(user.id, () => new UserNotFoundException(email))
_ <- accountTokenService.save(AccountToken.create(userId, email))
} yield {
Logger.info("Foo bar")
}
Stop Option propogation by failing the Future when option is None
Fail the future when id is none and abort
for {
....
accountOpt <-
user.id.map { id =>
Account.create(id, ...)
}.getOrElse {
Future.failed(new Exception("could not create account."))
}
...
} yield result
Better to have a custom exception like
case class NoIdException(msg: String) extends Exception(msg)
invoking .get on Option should be done only if you are sure that option is Some(x) otherwise .get will throw an exception.
Thats by using .get is not good practise because it may cause an exception in the code.
Instead of .get its good practice to use getOrElse.
You can map or flatMap the option to get access to the inner value.
Good practice
val x: Option[Int] = giveMeOption()
x.getOrElse(defaultValue)
Get can be used here
val x: Option[Int] = giveMeOption()
x.OrElse(Some(1)).get

Processing Set of Sets and return a flat Iterable

val input=Set(Set("a","b"),Set("b","c"))
I want this:
Map("a"->1,"b"->2,"c"->1)
What is the best functional approach for implementing such functionality?
Using yield keyword results in nested Iterables:
output = for(firstlevel<-input) yield for(item<-firstlevel) yield item
update: incorporated the suggestion to use input.toSeq.flatten
instead of input.toSeq flatMap { _.toSeq }
convert to a single sequence of values...
input.toSeq.flatten
...group values that match...
input.toSeq.flatten groupBy { identity }
...and count
input.toSeq.flatten groupBy { identity } mapValues { _.size }
If you want to use for-comprehension and yield:
output = for{
(set,idx) <- input.zipWithIndex
item <- set
} yield (item -> idx)
The code in your last line can be simplified (but does not what you want):
output = for{
set <- input
item <- set
} yield item
Oh boy, that's so ugly...
input.foldLeft(Map[String,Int]())((m,s) =>
s.foldLeft(m)((n,t) => n + (t -> (1 + n.getOrElse(t,0)))))
[Edit]
The Collection-API needs really a method for "merging" two Maps (or did I just overlook it???), e.g.
def merge[A,B](m1: Map[A,B], m2:Map[A,B])(f: (B,B)=>B):Map[A,B] =
m1.foldLeft(m2)((m,t) =>
m + (t._1 -> m.get(t._1).map(k => f(k,t._2)).getOrElse(t._2)))
With this you could write something like:
input.map(_.map(x => x -> 1).toMap).reduceLeft(merge(_,_)(_+_))
[Edit2]
With Kevin's idea merge could be written as
def merge[A,B](m1: Map[A,B], m2:Map[A,B])(f: (B,B)=>B):Map[A,B] =
m1.keys ++ m2.keys map {k => k ->
List(m1.get(k), m2.get(k)).flatten.reduceLeft(f)} toMap
Seems like my Scala-Fu is still too weak. What's the best way to express
(o1,o2) match {
case (Some(x),Some(y)) => Some(f(x,y))
case (Some(x), _) => Some(x)
case (_, Some(y)) => Some(y)
case => error("crack in the time-space-continuum")
}
?