Either, Options and for comprehensions

2020-03-20 17:43发布

问题:

I was coding a for comprehension, and wondered something:

def updateUserStats(user: User): Either[Error,User] = for {
  stampleCount <- stampleRepository.getStampleCount(user).right
  userUpdated <- Right(copyUserWithStats(user,stampleCount)).right // ?????????
  userSaved <- userService.update(userUpdated).right
} yield userSaved


def copyUserWithStats(user: User, stamples: Long): User = {
  val newStats = user.userStats.copy(stamples = stamples)
  user.copy(userStats = newStats)
}

It seems using copyUserWithStats which does not return an Either can't be used directly in the for comprehension, because it doesn't have the map/flatMap methods.

So I wonder, in this case, it is the appropriate solution to use Right(copyUserWithStats(user,stampleCount)).right

It seems to work at least...

By the way, I also tried with Option but it didn't work, can someone explain why?

def updateUserStats(user: User): Either[Error,User] = for {
  stampleCount <- stampleRepository.getStampleCount(user).right
  userUpdated <- Some(copyUserWithStats(user,stampleCount))
  userSaved <- userService.update(userUpdated).right
} yield userSaved

Thanks

回答1:

In a for-comprehension all monads have to be of the same kind. That means you can not mix RightProjection and Option, because the output has to be an Either. This is because the for-comprehension gets translated to a nested flatMap/map construct. Your example would look like this:

def updateUserStats(user: User): Either[Error,User] =
  stampleRepository.getStampleCount(user).right.flatMap { stampleCount =>
    Some(copyUserWithStats(user,stampleCount)).flatMap { userUpdated =>
      userService.update(userUpdated).right.map { userSaved =>
        userSaved
      }
    }
  }

If we now look at the signature of RightProjection.flatMap, which is def flatMap[AA >: A, Y](f: (B) ⇒ Either[AA, Y]): Either[AA, Y], we see, that the result has to be an Either, but flatMap of Option has the signature flatMap[B](f: (A) ⇒ Option[B]): Option[B]. It returns an Option and there is no sane way to translate an Option to an Either.

edit: Following example does not quiet work for Either, see link by huynhjl for more information

However you can besides extracting values from monads in a for-comprehension also create variables, so your example could be rewritten as:

def updateUserStats(user: User): Either[Error,User] = for {
  stampleCount <- stampleRepository.getStampleCount(user).right
  userUpdated = copyUserWithStats(user,stampleCount)
  userSaved <- userService.update(userUpdated).right
} yield userSaved

which saves us an unnecessary allocation and also makes the code more readable.