Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unsucking Error Handling with Futures

Unsucking Error Handling with Futures

Gary Coady

October 29, 2015
Tweet

Other Decks in Programming

Transcript

  1. def  userForEmail(email:  String):  Option[User]  =  ???   def  shoeSizeForUser(user:  User):

     Option[Size]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Option[ShoeStyle]    =  ???   def  recommendedShoe(email:  String):  Option[ShoeStyle]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  match  {      case  None  =>  println("No  shoe  recommendation")      case  Some(shoe)  =>  println(s"Shoe  recommendation:  $shoe")   }
  2. def  userForEmail(email:  String):  Future[User]  =  ???   def  shoeSizeForUser(user:  User):

     Future[Size]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[ShoeStyle]    =  ???   def  recommendedShoe(email:  String):  Future[ShoeStyle]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(shoe)  =>  println(s"Shoe  recommendation:  $shoe")   }
  3. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(userOpt:  Option[User]):

     Future[Option[Size]]  =  {      userOpt  match  {          case  None  =>  Future.successful(None)          case  Some(user)  =>  ???      }   }   def  recommendedShoeStyleForSize(sizeOpt:  Option[Size]):  Future[Option[ShoeStyle]]  =  {      sizeOpt  match  {          case  None  =>  Future.successful(None)          case  Some(size)  =>  ???      }   }   def  recommendedShoe(email:  String):  Future[Option[ShoeStyle]]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  4. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(userOpt:  Option[User]):

     Future[Option[Size]]  =  {      userOpt  match  {          case  None  =>  Future.successful(None)          case  Some(user)  =>  ???      }   }   def  recommendedShoeStyleForSize(sizeOpt:  Option[Size]):  Future[Option[ShoeStyle]]  =  {      sizeOpt  match  {          case  None  =>  Future.successful(None)          case  Some(size)  =>  ???      }   }   def  recommendedShoe(email:  String):  Future[Option[ShoeStyle]]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  5. –Dave Thomas “Every piece of knowledge must have a single,

    unambiguous, authoritative representation within a system.”
  6. def  flatMap[B](f:  A  =>  F[B]):  F[B]   Given  an  F[A]:

    case  class  FutureOption[+A](future:  Future[Option[A]])  {      def  flatMap[B](f:  A  =>  FutureOption[B]):  FutureOption[B]  =  {          val  result  =  future  flatMap  {              case  None  =>  Future.successful(None)              case  Some(opt)  =>  f(opt).future          }          FutureOption(result)      }   }
  7. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(user:  User):

     Future[Option[Size]]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[Option[ShoeStyle]]  =  ???   def  recommendedShoe(email:  String):  FutureOption[ShoeStyle]  =  for  {      user  <-­‐  FutureOption(userForEmail(email))      size  <-­‐  FutureOption(shoeSizeForUser(user))      shoe  <-­‐  FutureOption(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").future  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  8. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(user:  User):

     Future[Option[Size]]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[Option[ShoeStyle]]  =  ???   def  recommendedShoe(email:  String):  FutureOption[ShoeStyle]  =  for  {      user  <-­‐  FutureOption(userForEmail(email))      size  <-­‐  FutureOption(shoeSizeForUser(user))      shoe  <-­‐  FutureOption(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").future  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  9. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(user:  User):

     Future[Option[Size]]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[Option[ShoeStyle]]  =  ???   def  recommendedShoe(email:  String):  FutureOption[ShoeStyle]  =  for  {      user  <-­‐  FutureOption(userForEmail(email))      size  <-­‐  FutureOption(shoeSizeForUser(user))      shoe  <-­‐  FutureOption(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").future  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  10. OptionT[F[_], A] Wraps F[Option[A]] Works for any F[_] (as long

    as F[_] is a monad) Implements passing None through the F[_] effect e.g. OptionT[Future, A] is a wrapper for Future[Option[A]]
  11. import  scala.concurrent.Future   import  scala.concurrent.ExecutionContext.Implicits.global
 
 import  scalaz._   import

     Scalaz._   def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(user:  User):  Future[Option[Size]]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[Option[ShoeStyle]]  =  ???   def  recommendedShoe(email:  String):  OptionT[Future,  ShoeStyle]  =  for  {      user  <-­‐  OptionT(userForEmail(email))      size  <-­‐  OptionT(shoeSizeForUser(user))      shoe  <-­‐  OptionT(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").run  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   } libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.1.4"
  12. Either[A, B]: not nearly as useful as it could be.

    In scalaz, use: A \/ B — same as \/[A, B] In cats, use: A Xor B — same as Xor[A, B]
  13. def  userForEmail(email:  String):  Future[String  \/  User]  =  ???   def

     shoeSizeForUser(userOpt:  String  \/  User):  Future[String  \/  Size]  =  {      userOpt.fold(          err  =>  Future.successful(err.left),          user  =>  ???      )   }   def  recommendedShoeStyleForSize(sizeOpt:  String  \/  Size):  Future[String  \/  ShoeStyle]  =  {      sizeOpt.fold(          err  =>  Future.successful(err.left),          size  =>  ???      )   }   def  recommendedShoe(email:  String):  Future[String  \/  ShoeStyle]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  scala.util.Failure(t)  =>  println("Shoe  recommendation  failed")      case  scala.util.Success(res)  =>          res.fold(              err  =>  println(s"No  shoe  recommendation,  reason:  $err"),              shoe  =>  println(s"Shoe  recommendation:  $shoe")          )   }
  14. def  userForEmail(email:  String):  Future[String  \/  User]  =  ???   def

     shoeSizeForUser(userOpt:  String  \/  User):  Future[String  \/  Size]  =  {      userOpt.fold(          err  =>  Future.successful(err.left),          user  =>  ???      )   }   def  recommendedShoeStyleForSize(sizeOpt:  String  \/  Size):  Future[String  \/  ShoeStyle]  =  {      sizeOpt.fold(          err  =>  Future.successful(err.left),          size  =>  ???      )   }   def  recommendedShoe(email:  String):  Future[String  \/  ShoeStyle]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  scala.util.Failure(t)  =>  println("Shoe  recommendation  failed")      case  scala.util.Success(res)  =>          res.fold(              err  =>  println(s"No  shoe  recommendation,  reason:  $err"),              shoe  =>  println(s"Shoe  recommendation:  $shoe")          )   }
  15. def  userForEmail(email:  String):  Future[String  \/  User]  =  ???   def

     shoeSizeForUser(user:  User):  Future[String  \/  Size]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[String  \/  ShoeStyle]  =  ???   def  recommendedShoe(email:  String):  EitherT[Future,  String,  ShoeStyle]  =  for  {      user  <-­‐  EitherT(userForEmail(email))      size  <-­‐  EitherT(shoeSizeForUser(user))      shoe  <-­‐  EitherT(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").run  onComplete  {      case  scala.util.Failure(t)  =>  println("Shoe  recommendation  failed")      case  scala.util.Success(res)  =>          res.fold(              err  =>  println(s"No  shoe  recommendation,  reason:  $err"),              shoe  =>  println(s"Shoe  recommendation:  $shoe")          )   }
  16. Some(3) \/> "no value" == 3.right None \/> "no value"

    == "no value".left Converting Option to \/ (Either)
  17. type  Response[A]  =  EitherT[Future,  Result,  A] Response[String] EitherT[Future,  Result,  String]

    Response[User] Response[UserAndAuthInfo] EitherT[Future,  Result,  User] EitherT[Future,  Result,  UserAndAuthInfo]
  18. type  Response[A]  =  EitherT[Future,  Result,  A] Response[String] EitherT[Future,  Result,  String]

    Response[User] Response[UserAndAuthInfo] Response[Result] EitherT[Future,  Result,  User] EitherT[Future,  Result,  UserAndAuthInfo] EitherT[Future,  Result,  Result]
  19. EitherT[Future,  Result,  Result] def  merge(implicit  ev:  A  =:=  B)  =

     fold(left  =>  ev(left),  right  =>  right) Future[Result]
  20. def  response(block:  =>  Response[Result]):  Action[AnyContent]  =      Action.async(block.merge)  

    def  response[A](bodyParser:  BodyParser[A])(block:  Request[A]  =>  Response[Result]):  Action[A]  =      Action.async(bodyParser)(req  =>  block(req).merge)   def  response(block:  Request[AnyContent]  =>  Response[Result]):  Action[AnyContent]  =      response(BodyParsers.parse.default)(block)  
  21. type  Response[A]  =  EitherT[Future,  Result,  A]   object  Response  {

       def  fromFuture[A](o:  Future[A]):  Response[A]  =          EitherT(o.map(_.right))      def  fromFutureOption[A](noValue:  =>  Result)(o:  Future[Option[A]]):  Response[A]  =          EitherT(o.map(_  \/>  noValue))      def  fromFutureEither[A,  B](err:  A  =>  Result)(o:  Future[A  \/  B]):  Response[B]  =          EitherT(o.map(_.leftMap(err)))      def  fromOption[A](noValue:  =>  Result)(o:  Option[A]):  Response[A]  =          EitherT(Future.successful(o  \/>  noValue))      def  fromEither[A,  B](e:  A  =>  Result)(o:  A  \/  B):  Response[B]  =          EitherT(Future.successful(o.leftMap(e)))      def  fromTry[A](t:  Throwable  =>  Result)(o:  Try[A]):  Response[A]  =  {          val  eitherResult  =  o  match  {              case  scala.util.Success(s)  =>  s.right              case  scala.util.Failure(f)  =>  t(f).left          }          EitherT(Future.successful(eitherResult))      }   }
  22. def  getUserByEmail(email:  String):  Future[String  \/  User]  =  ???   def

     getFavouriteProducts(user:  User):  Future[String  \/  Seq[String]]  =  ???  def  productsByEmail(email:  String)  =  response  {          for  {              user          <-­‐  getUserByEmail(email)            |>  fromFutureEither(s  =>  InternalServerError(s))              products  <-­‐  getFavouriteProducts(user)  |>  fromFuture          }  yield  Ok(views.html.productsByEmail(user,  products))      } type  Response[A]  =  EitherT[Future,  Result,  A]   object  Response  {    def  fromFuture[A](o:  Future[A]):  Response[A]  =          EitherT(o.map(_.right))    def  fromFutureEither[A,  B](err:  A  =>  Result)(o:  Future[A  \/  B]):  Response[B]  =          EitherT(o.map(_.leftMap(err)))   } f(a) == a |> f
  23. sealed  trait  UserServiceError   private[controllers]  case  class  AuthError(why:  String)  extends

     UserServiceError   private[controllers]  case  object  ConnectionError  extends  UserServiceError   object  UserServiceError  {      def  authError(why:  String):  UserServiceError  =  AuthError(why)      val  connectionError:  UserServiceError  =  ConnectionError      def  fold[A](authError:  String  =>  A,  connectionError:  =>  A)(u:  UserServiceError)  =  {          u  match  {              case  AuthError(why)  =>  authError(why)              case  ConnectionError  =>  connectionError          }      }   }
  24. sealed  trait  UserServiceError   private[controllers]  case  class  AuthError(why:  String)  extends

     UserServiceError   private[controllers]  case  object  ConnectionError  extends  UserServiceError def  getUserByEmail(email:  String):  Future[UserServiceError  \/  User]  =  ???   def  getFavouriteProducts(user:  User):  Future[String  \/  Seq[String]]  =  ???    val  userServiceErrorToResult  =  UserServiceError.fold(          authError  =  Forbidden(_),          connectionError  =  InternalServerError("foobar")      )  _      def  productsByEmail(email:  String)  =  response  {          for  {              user          <-­‐  getUserByEmail(email)            |>  fromFutureEither(userServiceErrorToResult)              products  <-­‐  getFavouriteProducts(user)  |>  fromFuture          }  yield  Ok(views.html.productsByEmail(user,  products))      }