Scala - avoid too complex nested pattern matching

pattern-matching, scala, slick, spray

Solution

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
    }
}

Problem

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..

Original source