Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Unsucking Error Handling with Futures
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Gary Coady
October 29, 2015
Programming
20
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Unsucking Error Handling with Futures
Gary Coady
October 29, 2015
Other Decks in Programming
See All in Programming
残高管理から台帳サービスへの進化
artoy
1
200
Railsのためのパスキー実践入門 〜マルチテナントSaaSでの意思決定〜
utakaha
2
560
3回つくってわかった、 定期実行ジョブ基盤の本質情報
takai
0
480
見えないものを探る要求要件定義に必要な基本的思考 / invisible-requirement-thinking
minodriven
15
8.5k
動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for CSharp with Roslyn
prota
1
1.9k
Railsバージョンを4倍にした話
optim
0
370
設計次第でAIコードの読む量は減らせる / designing-for-code-reading
minodriven
35
17k
コードで差別化できない時代の ソフトウェアエンジニアリング / Software Engineering in an Era Where Code Doesn’t Differentiate
kishida
2
180
AIに “答え“を書かせるのを やめてみた
___akari11
0
120
Agents on Rails - Rails at Scale 2026
irinanazarova
0
560
golang-bob.pdf
iwatsukayura
0
130
全ての永続トークンを、生まれる前に消し去りたい!
sylph01
2
910
Featured
See All Featured
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.2k
Marketing to machines
jonoalderson
1
5.9k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.2k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
410
How to Talk to Developers About Accessibility
jct
2
580
The Language of Interfaces
destraynor
162
27k
Statistics for Hackers
jakevdp
799
230k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
850
30 Presentation Tips
portentint
PRO
1
430
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
23k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
590
Java REST API Framework Comparison - PWX 2021
mraible
34
9.8k
Transcript
Unsucking Errors with Futures Gary Coady <
[email protected]
>
Option[A] Error Handling: Possible Absence of a Value
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") }
Future[A] Asynchronous Computation
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") }
Future[Option[A]] Errors (possible missing values) + asynchronous computation
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") }
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") }
–Dave Thomas “Every piece of knowledge must have a single,
unambiguous, authoritative representation within a system.”
def flatMap[B](f: A => F[B]): F[B] Given an F[A]:
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) } }
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") }
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") }
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") }
N*N types = N2 Implementations?
Monad Transformers To the Rescue Scalaz cats
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]]
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"
Either[A, B] Errors with a “reason”
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]
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") ) }
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") ) }
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") ) }
Either vs Option
Some(3) \/> "no value" == 3.right None \/> "no value"
== "no value".left Converting Option to \/ (Either)
Representing Errors in Play Framework
type Response[A] = EitherT[Future, Result, A]
type Response[A] = EitherT[Future, Result, A] Response[String] EitherT[Future, Result, String]
type Response[A] = EitherT[Future, Result, A] Response[String] EitherT[Future, Result, String]
Response[User] EitherT[Future, Result, User]
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]
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]
EitherT[Future, Result, Result] def merge(implicit ev: A =:= B) =
fold(left => ev(left), right => right) Future[Result]
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)
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)) } }
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
Domain-specific Errors
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 } } }
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)) }
Questions?