Skip to content

Instantly share code, notes, and snippets.

@tpolecat
Last active March 9, 2022 01:09
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save tpolecat/a0b65e8ffdf5dc34a48f to your computer and use it in GitHub Desktop.
Save tpolecat/a0b65e8ffdf5dc34a48f to your computer and use it in GitHub Desktop.
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