Search code examples
scalafunctional-programmingfor-comprehension

ADT with for comprehension


I have an use case where I need to use for comprehension with ADT in Scala. I could write the same code using flatMaps but it seems a bit unreadable. Below is the piece of code.

case class MovieRecord(movie: Movie,
                       screenId: String,
                       availableSeats: Int,
                       reservedSeats: Option[Int] = None) {
  def movieInfo = new MovieInfoResponse(movie.imdbId, screenId, movie.title, availableSeats, reservedSeats.getOrElse(0))
}


sealed trait MovieBookingInformation
case class MovieBookingInformationFetched(bookMovie: MovieRecord) extends MovieBookingInformation
case object MovieBookingInformationFetchError extends MovieBookingInformation


def modifyBooking(reserveMovie: MovieSelected): Future[String] = {
 fetchRecordByImdbAndScreenId(reserveMovie.imdbId, reserveMovie.screenId) flatMap {
    case MovieBookingInformationFetched(m) if (m.availableSeats > 0) =>
      updateSeatReservationByImdbAndScreenId(m.copy(availableSeats = m.availableSeats - 1, reservedSeats = Some(m.reservedSeats.getOrElse(0) + 1))) flatMap {
        case MovieBookingUpdated(updatedMovieBooking) =>
          Future.successful(s"One seat reserved at Screen - ${updatedMovieBooking.screenId}")
        case MovieBookingUpdateFailed =>
          Future.successful(s"Movie seat reservation failed at screen ${reserveMovie.screenId}")
      }
    case MovieBookingInformationFetched(m) =>
      Future.successful(s"Sorry! No more seats available for ${m.movie.title} at Screen - ${m.screenId}")
    case MovieBookingInformationFetchError => Future.successful(s"No movie with IMDB ID ${reserveMovie.imdbId} found at ${reserveMovie.screenId}")
  }
}

In the above code, the next method is invoked on a resultant ADT content and result of if statement. How do I include the if statement in the for-comprehension to achieve the same.

Thanks in advance.


Solution

  • You can put pattern matching and if statements in a for comprehension on Future:

    for {
      MovieBookingInformationFetched(m) <- future1
      if m.availableSeats > 0
      MovieBookingUpdated(updatedMovieBooking) <- future2(m)
    } yield updatedMovieBooking
    

    However, this will be translated into Future.filter, so if the predicate is not satisfied, or the pattern cannot be matched, you'll end up with

    Future.failed(new NoSuchElementException("Future.filter predicate was not satisfied")
    

    You can then catch this failure in a recover statement, after the for-comp. The problem is that you want to catch three different "errors": the fact that there are no more available seats, the fact that fetchRecordByImdbAndScreenId can return a MovieBookingInformationFetchError, and the fact that updateSeatReservationByImdbAndScreenId can return a MovieBookingUpdateFailed.

    You won't be able to dissociate between these three using only for comprehension, unless you define custom exceptions, instead of custom result types (and recover these exceptions afterwards).