Scala - avoid too complex nested pattern matching - scala

I try to simplify validation process for serving responses for HTTP requests in Spray (I use Slick for database access). Currently, I check in one query if I should go further to the following query or not (return error). This end up with nested pattern matching. Every validation case can return different error so I can not use any flatMap.
class LocationDao {
val db = DbProvider.db
// Database tables
val devices = Devices.devices
val locations = Locations.locations
val programs = Programs.programs
val accessTokens = AccessTokens.accessTokens
def loginDevice(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = {
try {
db withSession { implicit session =>
val deviceRowOption = devices.filter(d => d.serialNumber === deviceSerialNumber).map(d => (d.id, d.currentLocationId.?, d.serialNumber.?)).firstOption
deviceRowOption match {
case Some(deviceRow) => {
val locationRowOption = locations.filter(l => l.id === deviceRow._2.getOrElse(0L) && l.login === login && l.password === password).firstOption
locationRowOption match {
case Some(locationRow) => {
val programRowOption = programs.filter(p => p.id === locationRow.programId).firstOption
programRowOption match {
case Some(programRow) => {
val program = Program(programRow.name, programRow.logo, programRow.moneyLevel, programRow.pointsForLevel,
programRow.description, programRow.rules, programRow.dailyCustomerScansLimit)
val locationData = LocationData(program)
val locationResponse = LocationResponse("access_token", System.currentTimeMillis(), locationData)
Right(locationResponse)
}
case None => Left(ProgramNotExistError)
}
}
case None => Left(IncorrectLoginOrPasswordError)
}
}
case None => Left(DeviceNotExistError)
}
}
} catch {
case ex: SQLException =>
Left(DatabaseError)
}
}
}
What is a good way to simplify this? Maybe there is other approach..

Generally, you can use a for-comprehension to chain together a lot of the monadic structures you have here (including Try, Option, and Either) without nesting. For example:
for {
val1 <- Try("123".toInt)
} yield for {
val2 <- Some(val1).map(_ * 2)
val3 = Some(val2 - 55)
val4 <- val3
} yield val4 * 2
In your style, this might otherwise have looked like:
Try("123".toInt) match {
case Success(val1) => {
val val2 = Some(val1).map(_ * 2)
val2 match {
case Some(val2value) => {
val val3 = Some(val2value - 55)
val3 match {
case Some(val4) => Some(val4)
case None => None
}
}
case None => None
}
case f:Failure => None
}
}

You can define a helper method for Eiterising your control flow.
The benefit of doing this is that you will have great control and flexibility in your flow.
def eitherMe[ I, T ]( eitherIn: Either[ Error, Option[ I ] ],
err: () => Error,
block: ( I ) => Either[ Error, Option[ T ] ]
): Either[ Error, Option[ T ] ] = {
eitherIn match {
case Right( oi ) => oi match {
case Some( i ) => block( i )
case None => Left( err() )
}
case Left( e ) => Left( e )
}
}
def loginDevice(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = {
try {
db withSession { implicit session =>
val deviceRowOption = devices.filter(d => d.serialNumber === deviceSerialNumber).map(d => (d.id, d.currentLocationId.?, d.serialNumber.?)).firstOption
val locationRowEither = eitherMe(
Right( deviceRowOption ),
() => { DeviceNotExistError },
deviceRow => {
val locationRowOption = locations.filter(l => l.id === deviceRow._2.getOrElse(0L) && l.login === login && l.password === password).firstOption
Right( locationRowOption )
}
)
val programRowEither = eitherMe(
locationRowEither,
() => { IncorrectLoginOrPasswordError },
locationRow => {
val programRowOption = programs.filter(p => p.id === locationRow.programId).firstOption
Right( programRowOption )
}
)
val locationResponseEither = eitherMe(
programRowEither,
() => { ProgramNotExistError },
programRow => {
val program = Program(programRow.name, programRow.logo, programRow.moneyLevel, programRow.pointsForLevel,
programRow.description, programRow.rules, programRow.dailyCustomerScansLimit)
val locationData = LocationData(program)
val locationResponse = LocationResponse("access_token", System.currentTimeMillis(), locationData)
Right(locationResponse)
}
)
locationResponseEither
}
} catch {
case ex: SQLException =>
Left(DatabaseError)
}
}

For me, when I sometime can't avoid nested complexity, I would extract out section of the code that make sense together and make it into a new method and give it a meaningful name. This will both document the code and make it more readable, and reduce the complexity inside each individual method. And usually, once I have done that, I am able to see the flow better and might be able to refactor it to make more sense (after first writing the tests to cover the behavior I want).
e.g. for your code, you can do something like this:
class LocationDao {
val db = DbProvider.db
// Database tables
val devices = Devices.devices
val locations = Locations.locations
val programs = Programs.programs
val accessTokens = AccessTokens.accessTokens
def loginDevice(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = {
try {
db withSession { implicit session =>
checkDeviceRowOption(deviceSerialNumber, login, password)
}
} catch {
case ex: SQLException =>
Left(DatabaseError)
}
}
def checkDeviceRowOption(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = {
val deviceRowOption = devices.filter(d => d.serialNumber === deviceSerialNumber).map(d => (d.id, d.currentLocationId.?, d.serialNumber.?)).firstOption
deviceRowOption match {
case Some(deviceRow) => {
val locationRowOption = locations.filter(l => l.id === deviceRow._2.getOrElse(0L) && l.login === login && l.password === password).firstOption
locationRowOption match {
case Some(locationRow) => { checkProgramRowOption(locationRow) }
case None => Left(IncorrectLoginOrPasswordError)
}
}
case None => Left(DeviceNotExistError)
}
}
def checkProgramRowOption(locationRow: LocationRowType): Either[Error, LocationResponse] = {
val programRowOption = programs.filter(p => p.id === locationRow.programId).firstOption
programRowOption match {
case Some(programRow) => {
val program = Program(programRow.name, programRow.logo, programRow.moneyLevel, programRow.pointsForLevel,
programRow.description, programRow.rules, programRow.dailyCustomerScansLimit)
val locationData = LocationData(program)
val locationResponse = LocationResponse("access_token", System.currentTimeMillis(), locationData)
Right(locationResponse)
}
case None => Left(ProgramNotExistError)
}
}
}
Note that this is just an illustration and probably won't compile because I don't have your lib, but you should be able to tweak the code for it to compile.

Related

scala Returns Future[Unit] instead of Future[ContentComponentModel]

I have the following code:
def getContentComponents: Action[AnyContent] = Action.async {
val test = contentComponentDTO.list().map { contentComponentsFuture =>
contentComponentsFuture.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 1 =>
println("blubb")
case 5 =>
contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(
text => {
contentComponentFuture.text = text.text
println(text.text)
println(contentComponentFuture.text)
}
)
}
)
}
Future.successful(Ok(Json.obj("contentComponents" -> test)))
}
and I got this error message:
The .list() method should return a Future[ContentComponentModel]
def list(): Future[Seq[ContentComponentModel]] = db.run {
whats my mistake in this case?
thanks
Your contentComponentsFuture should be of type Seq[ContentComponentModel]. In this case You should move
Future.successful(Ok(Json.obj("contentComponents" -> test)))
just into the map expression (which is async) after loop.
It should looks something like:
def getContentComponents: Action[AnyContent] = Action.async {
val test = contentComponentDTO.list().map { contentComponents =>
contentComponents.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 1 =>
println("blubb")
case 5 =>
contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(
text => {
contentComponentFuture.text = text.text
println(text.text)
println(contentComponentFuture.text)
}
)
}
)
Future.successful(Ok(Json.obj("contentComponents" -> contentComponents)))
}
}

Running more than one Future in Action.async

I tried in this way but it always go to case Nil.
def findByLastName(lastName:String)=Action.async {
val cursor = Json.obj("lastName" -> lastName)
StudentDaoAndEntity.findAllStudent(cursor) flatMap { lastName =>
ExaminationDao.findStudent(cursor) flatMap { lastName =>
LibraryDao.findStudent(cursor) map {
{
case Nil => Ok("Student Not Found")
case l: Seq[JsObject] => Ok(Json.toJson(l))
}
}
}
}
}
And my defined functions in Database are:
In StudentDaoAndEntity:
def findAllStudent(allStd: JsObject): Future[Seq[JsObject]] = {
// gather all the JsObjects in a list
collection.find(allStd).cursor[JsObject].collect[List]()
}
In LibraryDao:
def findStudent(allStd: JsObject): Future[Seq[JsObject]] = {
// gather all the JsObjects in a list
collection.find(allStd).cursor[JsObject].collect[List]()
}
In ExaminationDao:
def findStudent(allStd: JsObject): Future[Seq[JsObject]] = {
// gather all the JsObjects in a list
collection.find(allStd).cursor[JsObject].collect[List]()
}
The issue is with shadowing the lastName argument inside each lambda (as comments already pointed out). That being said, you can use a for-comprehension to make your code more readable
def findByLastName(lastName:String) = Action.async {
val cursor = Json.obj("lastName" -> lastName)
for {
_ <- StudentDaoAndEntity.findAllStudent(cursor)
_ <- ExaminationDao.findStudent(cursor)
students <- LibraryDao.findStudent(cursor)
} yield students match {
case Nil => Ok("Student Not Found")
case l: Seq[JsObject] => Ok(Json.toJson(l))
}
}
This is what I want:
def findByLastName(lastName:String)=Action.async {
request =>
val cursor = Json.obj("lastName" -> lastName)
StudentDaoAndEntity.findAllStudent(cursor) flatMap {
student =>
ExaminationDao.findStudent(cursor) flatMap {
examination =>
LibraryDao.findStudent(cursor) map {
{
case Nil => Ok("Student Not Found")
case library: Seq[JsObject] =>
val finalResult = student ++ examination ++ library
Ok(JsArray(finalResult))
}
}
}
}
}

slick 3.0 select then insert

I want to insert a new row with different status value if that row exists and return the new instance as Some to the caller. If it does not exist, nothing happens and return None. What I have is
def revoke(name: String, issueFor: String): Future[MyLicense] = {
val retrieveLicense = slickLicenses.filter(l => l.name === name && l.issueFor === issueFor).
sortBy(_.modifiedOn.desc).result.headOption
val insertLicense = for {
licenseOption <- retrieveLicense
} yield {
licenseOption match {
case Some(l) => Some(slickLicenses += l.copy(status = RevokedLicense))
case None => None
}
}
db.run(insertLicense).map {r =>
r.map { dbLicense => MyLicense(dbLicense.name, dbLicense.status)
}
}
How do I fix this?
Edit 1
I changed the code to
override def revoke(name: String, issueFor: String): Future[String] = {
val retrieveLicense = slickLicenses.filter(l => l.name === name && l.issueFor === issueFor).
sortBy(_.modifiedOn.desc).result.headOption
val insertLicenseAction = for {
l <- retrieveLicense
newLicense <- slickLicenses += l.get.copy(status = RevokedLicense)
} yield newLicense
db.run(insertLicenseAction).map(_ => name)
}
Hence, the question changes to how do I get the instance I insert into the database? Thanks
This is what I did finally,
override def revoke(name: String, issueFor: String): Future[String] = {
val retrieveLicense = slickLicenses.filter(l => l.name === name && l.issueFor === issueFor).
sortBy(_.modifiedOn.desc).result.headOption
val insertLicenseAction = for {
l <- retrieveLicense
_ <- l.map(x => slickLicenses += x.copy(status = RevokedLicense,
modifiedOn = new Timestamp(System.currentTimeMillis())))
.getOrElse(DBIO.successful(l))
} yield ()
db.run(insertLicenseAction).map(x => name)}

Can this be made prettier? Matching string to case class field

Sorry about the title, please edit it to be more descriptive if you can!
Is there a way to generalize this with scala? I have quite a few fields that can be filtered against, and this is just plain ugly! The problem I ran against was matching parameter name against the case class field, can it be done in a more general way, without this much code duplication?
get("/MostClicked") { request =>
val res = MongoDbOps.findMostClicked()
val res1 = request.params.get("source") match {
case None => res
case Some(f) => res.filter(_.source == f)
}
val res2 = request.params.get("category") match {
case None => res1
case Some(f) => res1.filter(_.category == f)
}
// more of the same...
render.plain {
res2.toJson.prettyPrint
}.toFuture
}
You can try either of the two approaches below.
case class MostClicked(
source: String,
category: String,
rating: String)
object MongoDbOps {
def findMostClicked() = List[MostClicked]()
}
class Request {
val params = Map[String, String]()
}
def get(path: String)(f: Request => String) = {
f(new Request)
}
First is to use a List of matchers and then apply them sequentially using foldLeft:
get("/MostClicked") { request =>
val res = MongoDbOps.findMostClicked()
val kfun = List(
"source" -> ((x: MostClicked, y: String) => x.source == y),
"category" -> ((x: MostClicked, y: String) => x.category == y),
"rating" -> ((x: MostClicked, y: String) => x.rating == y))
val r = kfun.foldLeft(res) { (x, y) =>
request.params.get(y._1)
.map(f => res.filter(y._2(_, f)))
.getOrElse(x)
}
r.toString
// more of the same...
render.plain {
r.toJson.prettyPrint
}.toFuture
}
Or simply make it more readable:
get("/MostClicked") { request =>
val res = MongoDbOps.findMostClicked()
val res1 = request.params.get("source")
.map(f => res.filter(_.source == f))
.getOrElse(res)
val res2 = request.params.get("category")
.map(f => res.filter(_.category == f))
.getOrElse(res1)
val res3 = request.params.get("rating")
.map(f => res.filter(_.rating == f))
.getOrElse(res2)
// more of the same...
render.plain {
res3.toJson.prettyPrint
}.toFuture
}
I had to break it down to parts and work out the types, thus the smaller methods.
It works in my simple experiment and gets rid of the duplication where it can, but might not be as readable?
Note that unless you get into reflection, you still need to create the name of the parameter and how it should be filtered.
This looks to be the same as tuxdna's answer except with types to increase readability and maintainability
SETUP
case class Request(params: Map[String, String])
case class Result(category: String, source: String)
type Filterer = (Result, String) => Boolean
case class FilterInfo(paramName: String, filterer: Filterer)
type Analyzer = FilterInfo => List[Result]
val request = Request(Map("source"->"b"))
EXTRACTION METHODS
def reduce(filterInfos: List[FilterInfo], results: List[Result]) = {
filterInfos.foldLeft(results) { (currentResult, filterInfo) =>
request.params.get(filterInfo.paramName)
.map(filterVal => currentResult.filter(filterInfo.filterer(_, filterVal)))
.getOrElse(currentResult)
}
}
USAGE
val filterInfos = List(
FilterInfo("source", (result, filterVal) => result.source == filterVal),
FilterInfo("category", (result, filterVal) => result.category == filterVal))
val res = List(Result("a","a"), Result("b", "b"))
reduce(filterInfos, res)
Used in your example it would be more like this:
get("/MostClicked") { request =>
val res = MongoDbOps.findMostClicked()
val filterInfos = List(
FilterInfo("source", (result, filterVal) => result.source == filterVal),
FilterInfo("category", (result, filterVal) => result.category == filterVal))
val finalResult = reduce(filterInfos, res)
render.plain {
finalResult.toJson.prettyPrint
}.toFuture
}

How to check if there's None in List[Option[_]] and return the element's name?

I have multiple Option's. I want to check if they hold a value. If an Option is None, I want to reply to user about this. Else proceed.
This is what I have done:
val name:Option[String]
val email:Option[String]
val pass:Option[String]
val i = List(name,email,pass).find(x => x match{
case None => true
case _ => false
})
i match{
case Some(x) => Ok("Bad Request")
case None => {
//move forward
}
}
Above I can replace find with contains, but this is a very dirty way. How can I make it elegant and monadic?
Edit: I would also like to know what element was None.
Another way is as a for-comprehension:
val outcome = for {
nm <- name
em <- email
pwd <- pass
result = doSomething(nm, em, pwd) // where def doSomething(name: String, email: String, password: String): ResultType = ???
} yield (result)
This will generate outcome as a Some(result), which you can interrogate in various ways (all the methods available to the collections classes: map, filter, foreach, etc.). Eg:
outcome.map(Ok(result)).orElse(Ok("Bad Request"))
val ok = Seq(name, email, pass).forall(_.isDefined)
If you want to reuse the code, you can do
def allFieldValueProvided(fields: Option[_]*): Boolean = fields.forall(_.isDefined)
If you want to know all the missing values then you can find all missing values and if there is none, then you are good to go.
def findMissingValues(v: (String, Option[_])*) = v.collect {
case (name, None) => name
}
val missingValues = findMissingValues(("name1", option1), ("name2", option2), ...)
if(missingValues.isEmpty) {
Ok(...)
} else {
BadRequest("Missing values for " + missingValues.mkString(", ")))
}
val response = for {
n <- name
e <- email
p <- pass
} yield {
/* do something with n, e, p */
}
response getOrElse { /* bad request /* }
Or, with Scalaz:
val response = (name |#| email |#| pass) { (n, e, p) =>
/* do something with n, e, p */
}
response getOrElse { /* bad request /* }
if ((name :: email :: pass :: Nil) forall(!_.isEmpty)) {
} else {
// bad request
}
I think the most straightforward way would be this:
(name,email,pass) match {
case ((Some(name), Some(email), Some(pass)) => // proceed
case _ => // Bad request
}
A version with stone knives and bear skins:
import util._
object Test extends App {
val zero: Either[List[Int], Tuple3[String,String,String]] = Right((null,null,null))
def verify(fields: List[Option[String]]) = {
(zero /: fields.zipWithIndex) { (acc, v) => v match {
case (Some(s), i) => acc match {
case Left(_) => acc
case Right(t) =>
val u = i match {
case 0 => t copy (_1 = s)
case 1 => t copy (_2 = s)
case 2 => t copy (_3 = s)
}
Right(u)
}
case (None, i) =>
val fails = acc match {
case Left(f) => f
case Right(_) => Nil
}
Left(i :: fails)
}
}
}
def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass"
def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _)
val name:Option[String] = Some("Bob")
val email:Option[String]= None
val pass:Option[String] = Some("boB")
val res = verify(List(name,email,pass))
res.fold(fail, (consume _).tupled)
val res2 = verify(List(name, Some("bob#bob.org"),pass))
res2.fold(fail, (consume _).tupled)
}
The same thing, using reflection to generalize the tuple copy.
The downside is that you must tell it what tuple to expect back. In this form, reflection is like one of those Stone Age advances that were so magical they trended on twitter for ten thousand years.
def verify[A <: Product](fields: List[Option[String]]) = {
import scala.reflect.runtime._
import universe._
val MaxTupleArity = 22
def tuple = {
require (fields.length <= MaxTupleArity)
val n = fields.length
val tupleN = typeOf[Tuple2[_,_]].typeSymbol.owner.typeSignature member TypeName(s"Tuple$n")
val init = tupleN.typeSignature member nme.CONSTRUCTOR
val ctor = currentMirror reflectClass tupleN.asClass reflectConstructor init.asMethod
val vs = Seq.fill(n)(null.asInstanceOf[String])
ctor(vs: _*).asInstanceOf[Product]
}
def zero: Either[List[Int], Product] = Right(tuple)
def nextProduct(p: Product, i: Int, s: String) = {
val im = currentMirror reflect p
val ts = im.symbol.typeSignature
val copy = (ts member TermName("copy")).asMethod
val args = copy.paramss.flatten map { x =>
val name = TermName(s"_$i")
if (x.name == name) s
else (im reflectMethod (ts member x.name).asMethod)()
}
(im reflectMethod copy)(args: _*).asInstanceOf[Product]
}
(zero /: fields.zipWithIndex) { (acc, v) => v match {
case (Some(s), i) => acc match {
case Left(_) => acc
case Right(t) => Right(nextProduct(t, i + 1, s))
}
case (None, i) =>
val fails = acc match {
case Left(f) => f
case Right(_) => Nil
}
Left(i :: fails)
}
}.asInstanceOf[Either[List[Int], A]]
}
def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass"
def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _)
val name:Option[String] = Some("Bob")
val email:Option[String]= None
val pass:Option[String] = Some("boB")
type T3 = Tuple3[String,String,String]
val res = verify[T3](List(name,email,pass))
res.fold(fail, (consume _).tupled)
val res2 = verify[T3](List(name, Some("bob#bob.org"),pass))
res2.fold(fail, (consume _).tupled)
I know this doesn't scale well, but would this suffice?
(name, email, pass) match {
case (None, _, _) => "name"
case (_, None, _) => "email"
case (_, _, None) => "pass"
case _ => "Nothing to see here"
}