Add side effects to scala yield statement - scala

In the following scala for loop
private val tpath = for (csvPath <- CsvPaths
if new java.io.File(csvPath).exists()
) yield csvPath
I would like to add a println side effect - similar to the following:
private val tpath = for (csvPath <- CsvPaths
if new java.io.File(csvPath).exists() { // Following is illegal syntax
println(s"Following path exists $csvPath")
}
) yield csvPath
So is there any syntax for adding the side effect to the for/yield loop?

You can use an _ assignment:
for {
csvPath <- CsvPaths
if (new java.io.File(csvPath).exists())
_ = println(s"Following path exists $csvPath")
} yield csvPath
Of course for this specific example you could just use a block for the yield:
for {
csvPath <- CsvPaths
if (new java.io.File(csvPath).exists())
} yield {
println(s"Following path exists $csvPath")
csvPath
}
but the above technique is useful if you want to put calls in the "middle" of a for/yield chain with more <- lines afterwards.

for {
csvPath <- CsvPaths
_ = if(new java.io.File(csvPath).exists()) println(...)
} yield csvPath
or
for {
csvPath <- CsvPaths
} yield {
if(new java.io.File(csvPath).exists()) println(...)
csvPath
}

Related

Difference between { zip map } and { flatMap map } in Future of Scala

I'm reading 《hands on scala》, and one of its exercise is parallelizing merge sort.
I want to know why for-comprehension, which can be translated into flatMap and map, takes more time than zip and map.
my code:
def mergeSortParallel0[T: Ordering](items: IndexedSeq[T]): Future[IndexedSeq[T]] = {
if (items.length <= 16) Future.successful(mergeSortSequential(items))
else {
val (left, right) = items.splitAt(items.length / 2)
for (
l <- mergeSortParallel0(left);
r <- mergeSortParallel0(right)
) yield merge(l, r)
}
}
the standard answer provided by book:
def mergeSortParallel0[T: Ordering](items: IndexedSeq[T]): Future[IndexedSeq[T]] = {
if (items.length <= 16) Future.successful(mergeSortSequential(items))
else {
val (left, right) = items.splitAt(items.length / 2)
mergeSortParallel0(left).zip(mergeSortParallel0(right)).map{
case (sortedLeft, sortedRight) => merge(sortedLeft, sortedRight)
}
}
}
flatMap or map are sequential operations on Scala Future and on their own have nothing to do with running things in parallel. They can be viewed as simple callbacks executed when a Future completes. Or in other words, provided code inside map(...) or flatMap(...) will start to execute only when the previous Future is finished.
zip on the other hand will run your Futures in parallel and return the result as a Tuple when both of them are complete. Similarly, you could use zipWith which takes a function to transform the results of two Futures (combines zip and map operations):
mergeSortParallel0(left).zipWith(mergeSortParallel0(right)){
case (sortedLeft, sortedRight) => merge(sortedLeft, sortedRight)
}
Another way to achieve parallelism is to declare Futures outside for-comprehension. This works as Futures in Scala are 'eager' and they start as soon as you declare them (assign to val):
def mergeSortParallel0[T: Ordering](items: IndexedSeq[T]): Future[IndexedSeq[T]] = {
if (items.length <= 16) Future.successful(mergeSortSequential(items))
else {
val (left, right) = items.splitAt(items.length / 2)
val leftF = mergeSortParallel0(left)
val rightF = mergeSortParallel0(right)
for {
sortedLeft <- leftF
sortedRight <- rightF
} yield {
merge(sortedLeft, sortedRight)
}
}
}

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 syntax problems in for comprehension statement

Is it possible to make an if statement in a for block?
I have the following:
val process = for {
stepIds <- processTemplateDTO.getProcessStepTemplateIds(processTemplateId)
allApprovedProcessTemplates <- processTemplateDTO.getApprovedProcessTemplates(clientId) //Get all approved process templates
processTemplate <- processTemplateDTO.getProcessTemplate(processTemplateId, clientId) // Get the Process Template
prerequisites <- getProcessTemplateForEdit(processPrerequisitesDTO.getProcessPrerequisiteProcessTemplateIdsByProcessTemplateId(processTemplateId), clientId)
postConditions <- getProcessTemplateForEdit(processPostConditionsDTO.getProcessPostConditionProcessTemplateIdsByProcessTemplateId(processTemplateId), clientId)
approvedProcessTemplate <- processTemplateDTO.getProcessTemplate(processTemplate.get.approveprocess, clientId)
if (processTemplate.get.trainingsprocess.isDefined) {
trainedProcessTemplate <- processTemplateDTO.getProcessTemplate(processTemplate.get.trainingsprocess.get, clientId)
}
And I only want to call the processTemplateDTO.getProcessTemplate if processTemplate.get.trainingsprocess.isDefined is true
is this possible?
Thanks
also tried this way:
trainedProcessTemplate <- {
if (processTemplate.get.trainingsprocess.isDefined) {
processTemplateDTO.getProcessTemplate(processTemplate.get.trainingsprocess.get, clientId)
} else {
None
}
}
UPDATE
trainedProcessTemplate <- Nil
_ = if (processTemplate.get.trainingsprocess.get != null) {
processTemplateDTO.getProcessTemplate(processTemplate.get.trainingsprocess.get, clientId)
}
Yes, It is possible.
You can see the below code for reference -
val list = List(1,2,3,4)
val result = for {
id <- list
_ = if (id < 2) {
println(s"Hello I am $id")
}
} yield id
Yes it is possible to use if block in for block, there are two ways
for {
a <- somework1 // returns boolean
b <- somework2 if(a)
} yield (a, b) //anything you like
or you can try
for {
a <- somework1 // returns boolean
b <- if(a) somework2 else somework3
} yield (a, b) //anything you like
In 2nd way you have to give else block as well because without that it will return Any
Hope this helps!

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

Scala Option return type

I am newbie in Scala programming world but loving it. Recently I have started porting my research App into Scala and one of thing I am still struggling is the return keyword. For example in below code
def readDocument(dbobj:MongoDBObject) = Option[ContainerMetaData]
{
for(a <- dbobj.getAs[String]("classname");
b <- dbobj.getAs[Long]("id");
c <- dbobj.getAs[Long]("version");
d <- dbobj.getAs[String]("description");
e <- dbobj.getAs[String]("name");
f <- dbobj.getAs[String]("tag");
g <- dbobj.getAs[Int]("containertype");
h <- dbobj.getAs[Date]("createddate")
)
{
val ctype = ContainerType(g)
val jodadt = new DateTime(h)
val data = new ContainerMetaData(a,b,c,d,e,f,ctype,jodadt)
Some(data)
}
None
}
In above code I get the error message:
type mismatch; found : None.type required: om.domain.ContainerMetaData
So if I remove the explicit return type the code works but then without explicit return keyword I am not able to terminate my code at Some(data).
def readDocument(dbobj:MongoDBObject)=
{
for(a <- dbobj.getAs[String]("classname");
b <- dbobj.getAs[Long]("id");
c <- dbobj.getAs[Long]("version");
d <- dbobj.getAs[String]("description");
e <- dbobj.getAs[String]("name");
f <- dbobj.getAs[String]("tag");
g <- dbobj.getAs[Int]("containertype");
h <- dbobj.getAs[Date]("createddate")
)
{
val ctype = ContainerType(g)
val jodadt = new DateTime(h)
val data = new ContainerMetaData(a,b,c,d,e,f,ctype,jodadt)
Some(data)
}
None
}
And if add a return keyword then compiler complains
method `readDocument` has return statement; needs result tye
Few more additional info, this is the trait I am extending
trait MongoDAOSerializer[T] {
def createDocument(content:T) : DBObject
def readDocument(db:MongoDBObject) : Option[T]
}
The problem is, that you are missing the yield keyword in the for-comprehension. And also the None at the end is unnecessary, as the for-comprehension will yield None, if one of the values is missing and also the explicit creation of a Some in the comprehension is not needed, as it will create an Option anyway. Your code hase to look like this (not tested)
def readDocument(dbobj: MongoDBObject): Option[ContainerMetaData] = {
for {
a <- dbobj.getAs[String]("classname")
b <- dbobj.getAs[Long]("id")
c <- dbobj.getAs[Long]("version")
d <- dbobj.getAs[String]("description")
e <- dbobj.getAs[String]("name")
f <- dbobj.getAs[String]("tag")
g <- dbobj.getAs[Int]("containertype")
h <- dbobj.getAs[Date]("createddate")
} yield {
val ctype = ContainerType(g)
val jodadt = new DateTime(h)
new ContainerMetaData(a,b,c,d,e,f,ctype,jodadt)
}
}