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
自分的「カンファレンスの楽しみ方」
syumai
0
200
DynamoDBの基礎を振り返りながらベクトル検索機能を理解する
musan
3
270
AIの中の人になってみる
htkym
0
170
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
170
PyO3 で既存 Python 評価器を Rust core 化する ー wasm-bindgen でブラウザにも配るための設計
kdash
1
560
Kiroで創り、AgentCoreで繋ぐ!AWSで実践する「AI-DLC」から「AIエージェント統合」までの最新地図
licux
3
380
AIは賢い。でも実行環境は? CLIおじさんがAI時代に伝えたいこと ~ CLIおじさんがAI時代に伝えたいこと ~
curekoshimizu
1
220
GraphRAGのKnowledge Graphを 直接!見る/View-GraphRAG's-KnowledgeGraph-directly!
tyumugi1113
1
270
AI時代に学ぶ 好きなルール 嫌いなルール Linter編
shorty5121
0
890
FastAPI の並行処理モデルを完全に理解する
hoto17296
9
3.8k
go-spidermonkeyでAIエージェントのCode Modeを実装する
syumai
3
1.5k
tsc.rip を支える技術 / Kyoto.なんか #8
susisu
0
4.4k
Featured
See All Featured
Exploring anti-patterns in Rails
aemeredith
3
500
WENDY [Excerpt]
tessaabrams
12
39k
First, design no harm
axbom
PRO
2
1.3k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
42k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
270
The Cost Of JavaScript in 2023
addyosmani
55
10k
How GitHub (no longer) Works
holman
316
150k
Code Review Best Practice
trishagee
74
20k
Marketing to machines
jonoalderson
1
5.8k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
500
From π to Pie charts
rasagy
0
360
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.4k
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?