Search code examples
scalascala-cats

How to map an Option inside a for comprehension with EitherT


Hi I am trying to execute a for comprehension like

(for {
  player <- playerRepository.findById(playerId) // findById returns EitherT[Future, String, Player]
  teamOpt <- teamRepository.findByPlayer(playerId) // findByPlayer returns EitherT[Future, String, Option[Team]]
  playedMatches <- teamOpt.map(team => playedMatchesRepository.findByTeamId(team.id)) // findByTeamId returns EitherT[Future, String, Seq[PlayedMatches]]
} yield (
  player,
  teamOpt,
  playedMatches
)).fold(
  error => {
    logger.error(s"all error: $error")
    Left(error)
  },
  tuple => {
    logger.debug(s"get success -> $tuple")
    Right(playerDetailResponse(tuple._1, tuple._2, tuple._3))
  }
)

I can not get a corret structure for

playedMatches <- teamOpt.map(team => playedMatchesRepository.findByTeamId(team.id))

I am getting the following error when I compile the project

[error] /Users/agusgambina/code/soccer/app/services/impl/PlayerServiceImpl.scala:28:17: type mismatch;
[error]  found   : Option[(models.Player, Option[models.Team], cats.data.EitherT[scala.concurrent.Future,String,Seq[models.PlayedMatches]])]
[error]  required: cats.data.EitherT[scala.concurrent.Future,?,?]
[error]       playedMatches <- teamOpt.map(team => playedMatchesRepository.findByTeamId(team.id))
[error]                 ^
[error] one error found

I tried to wrap


Solution

  • playedMatches <- teamOpt.map(team => playedMatchesRepository.findByTeamId(team.id)) // findByTeamId returns EitherT[Future, String, Seq[PlayedMatches]]
    

    in here, you are getting an Option[EitherT[Future, String, Seq[PlayedMatches]]] which doesn't compose with the EitherT[Future, String, ???] you are using as Monad for the for comprehension.

    one option you have is to actually use a fold on teamOpt.

    teamOpt.fold(EitherT(Future.successful(Left("Team not Found"): Either[String, Team]))){ team => playedMatchesRepository.findByTeamId(team.id) }
    

    This way you unwrap the Option with the error case if is empty or the success case if non-empty. (create a function that takes the teamOPt as parameter and the for-comprehension will look much better)

    Hope it helps

    update In case of the empty case be successful, and be happy returning an empty sequence:

    teamOpt.fold(
      EitherT(Future.successful(Right(Seq()): Either[String, Seq[PlayedMatches]))
    ){ team =>
      playedMatchesRepository.findByTeamId(team.id) 
    }