-
-
Save tpolecat/a0b65e8ffdf5dc34a48f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.concurrent.Future | |
import scala.concurrent.ExecutionContext.Implicits.global | |
object kevin { | |
sealed trait LoginFailure | |
case object InvalidCredentials extends LoginFailure | |
case object AuthServerDown extends LoginFailure | |
sealed trait AddUserFailure | |
case class DuplicateUser(x: Long, name: String) extends AddUserFailure | |
case class NickNameTooShort(nickname: String) extends AddUserFailure | |
// The original attempt is awkward | |
object attempt1 { | |
def login(user: String, password: String): Future[Either[LoginFailure, String]] = | |
Future.successful( Right("foobar") ) | |
def addUser(name: String, nickname: String, token: String): Future[Either[AddUserFailure, Long]] = ??? | |
Future.successful(Right(55)) | |
val example: Future[Either[Object, Long]] = | |
for { | |
loginResult <- login("fizz", "buzz") | |
adddedUser <- loginResult.fold(failed => Future.successful(Left(failed)), | |
token => addUser("Buzz", "Buzzy", token)) | |
} yield adddedUser | |
} | |
// Switch to scalaz.\/ | |
object attempt2 { | |
import scalaz._, Scalaz._ | |
def login(user: String, password: String): Future[LoginFailure \/ String] = | |
Future.successful("foobar".right) | |
def addUser(name: String, nickname: String, token: String): Future[AddUserFailure \/ Long] = | |
Future.successful(55L.right) | |
// error type remains useless | |
val example: Future[Object \/ Long] = | |
for { | |
loginResult <- login("fizz", "buzz") | |
adddedUser <- loginResult.fold(failed => Future.successful(failed.left), | |
token => addUser("Buzz", "Buzzy", token)) | |
} yield adddedUser | |
} | |
// Unify the failure types | |
object attempt3 { | |
import scalaz._, Scalaz._ | |
import attempt2.{ login, addUser } | |
// A failure type that subsumes both | |
type FailureType = LoginFailure \/ AddUserFailure | |
def liftLoginFailure[A](fa: Future[LoginFailure \/ A]): Future[FailureType \/ A] = | |
fa.map(_.leftMap(_.left)) | |
def liftAddUserFailure[A](fa: Future[AddUserFailure \/ A]): Future[FailureType \/ A] = | |
fa.map(_.leftMap(_.right)) | |
// Failure types unify to FailureType now | |
val example: Future[FailureType \/ Long] = | |
for { | |
loginResult <- liftLoginFailure(login("fizz", "buzz")) | |
adddedUser <- loginResult.fold(failed => Future.successful(failed.left[Long]), | |
token => liftAddUserFailure(addUser("Buzz", "Buzzy", token))) | |
} yield adddedUser | |
} | |
// Use EitherT to lift the fast-fail effect to the top | |
object attempt4 { | |
import scalaz._, Scalaz._ | |
import attempt2.{ login, addUser } | |
import attempt3.FailureType | |
// Lift to EitherT ... simpler! | |
def liftLoginFailure[A](fa: Future[LoginFailure \/ A]): EitherT[Future, FailureType, A] = | |
EitherT(fa).leftMap(_.left) | |
def liftAddUserFailure[A](fa: Future[AddUserFailure \/ A]): EitherT[Future, FailureType, A] = | |
EitherT(fa).leftMap(_.right) | |
// Use EitherT just like \/ and "forget" that the outer type is Future | |
val exampleT: EitherT[Future, FailureType, Long] = | |
for { | |
token <- liftLoginFailure(login("fizz", "buzz")) | |
adddedUser <- liftAddUserFailure(addUser("Buzz", "Buzzy", token)) | |
} yield adddedUser | |
// .run to flip Future back to the top | |
val example: Future[FailureType \/ Long] = | |
exampleT.run | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment